<commit_before>game.TitleScreen = me.ScreenObject.extend({
    init: function(){
        this._super(me.ScreenObject, 'init');
        this.font = null;
        this.ground1 = null;
        this.ground2 = null;
        this.logo = null;
    },

    onResetEvent: function() {
        me.audio.stop("theme");
        game.data.newHiScore = false;

        me.game.world.addChild(new BackgroundLayer('bg', 1));
        me.input.bindKey(me.input.KEY.ENTER, "enter", true);
        me.input.bindKey(me.input.KEY.SPACE, "enter", true);
        me.input.bindPointer(me.input.mouse.LEFT, me.input.KEY.ENTER);

        this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) {
            if (action === "enter") {
                me.state.change(me.state.PLAY);
            }
        });

        //logo
        var logoImg = me.loader.getImage('logo');
        this.logo = new me.Sprite(
            me.game.viewport.width/2 - 170, ///MODIFY LOGO HORIZONTAL
            -logoImg,
            {image: logoImg}
        );
        me.game.world.addChild(this.logo, 10);

        var that = this;
        var logoTween = me.pool.pull("me.Tween", this.logo.pos)
            .to({y: me.game.viewport.height/4 - 100}, 1000)/// MODIFY LOGO VERTICAL
            .easing(me.Tween.Easing.Exponential.InOut).start();

        this.ground1 = me.pool.pull("ground", 0, me.video.renderer.getHeight() - 96);
        this.ground2 = me.pool.pull("ground", me.video.renderer.getWidth(),
                                    me.video.renderer.getHeight() - 96);
        me.game.world.addChild(this.ground1, 11);
        me.game.world.addChild(this.ground2, 11);

        me.game.world.addChild(new (me.Renderable.extend ({
            // constructor
            init: function() {
                // size does not matter, it's just to avoid having a zero size
                // renderable
                this._super(me.Renderable, 'init', [0, 0, 100, 100]);
                this.text = me.device.touch ? 'Tap to start' : 'PRESS SPACE OR CLICK LEFT MOUSE BUTTON TO START \n\t\t\t\t\t\t\t\t\t\t\tPRESS "M" TO MUTE SOUND';
                this.font = new me.Font('gamefont', 20, '#000');
            },
            draw: function (renderer) {
                var measure = this.font.measureText(renderer, this.text);
                var xpos = me.game.viewport.width/2 - measure.width/2;
                var ypos = me.game.viewport.height-50;//height/2 + 50;
                this.font.draw(renderer, this.text, xpos, ypos);
            }
        })), 12);
    },

    onDestroyEvent: function() {
        // unregister the event
        me.event.unsubscribe(this.handler);
        me.input.unbindKey(me.input.KEY.ENTER);
        me.input.unbindKey(me.input.KEY.SPACE);
        me.input.unbindPointer(me.input.mouse.LEFT);
        this.ground1 = null;
        this.ground2 = null;
        me.game.world.removeChild(this.logo);
        this.logo = null;
    }
});
<commit_msg>title screen modifications<commit_after>game.TitleScreen = me.ScreenObject.extend({
    init: function(){
        this._super(me.ScreenObject, 'init');
        this.font = null;
        this.ground1 = null;
        this.ground2 = null;
        this.logo = null;
    },

    onResetEvent: function() {
        me.audio.stop("theme");
        game.data.newHiScore = false;

        me.game.world.addChild(new BackgroundLayer('bg', 1));
        me.input.bindKey(me.input.KEY.ENTER, "enter", true);
        me.input.bindKey(me.input.KEY.SPACE, "enter", true);
        me.input.bindPointer(me.input.mouse.LEFT, me.input.KEY.ENTER);

        this.handler = me.event.subscribe(me.event.KEYDOWN, function (action, keyCode, edge) {
            if (action === "enter") {
                me.state.change(me.state.PLAY);
            }
        });

        //logo
        var logoImg = me.loader.getImage('logo');
        this.logo = new me.Sprite(
            me.game.viewport.width/2 - 275, ///MODIFY LOGO HORIZONTAL
            -logoImg,
            {image: logoImg}
        );
        me.game.world.addChild(this.logo, 10);

        var that = this;
        var logoTween = me.pool.pull("me.Tween", this.logo.pos)
            .to({y: me.game.viewport.height/4 - 100}, 1000)/// MODIFY LOGO VERTICAL
            .easing(me.Tween.Easing.Exponential.InOut).start();

        this.ground1 = me.pool.pull("ground", 0, me.video.renderer.getHeight() - 96);
        this.ground2 = me.pool.pull("ground", me.video.renderer.getWidth(),
                                    me.video.renderer.getHeight() - 96);
        me.game.world.addChild(this.ground1, 11);
        me.game.world.addChild(this.ground2, 11);

        me.game.world.addChild(new (me.Renderable.extend ({
            // constructor
            init: function() {
                // size does not matter, it's just to avoid having a zero size
                // renderable
                this._super(me.Renderable, 'init', [0, 0, 100, 100]);
                this.text = me.device.touch ? 'Tap to start' : 'PRESS SPACE OR CLICK LEFT MOUSE BUTTON TO START \n\t\t\t\t\t\t\t\t\t\t\tPRESS "M" TO MUTE SOUND';
                this.font = new me.Font('gamefont', 20, '#000');
            },
            draw: function (renderer) {
                var measure = this.font.measureText(renderer, this.text);
                var xpos = me.game.viewport.width/2 - measure.width/2;
                var ypos = me.game.viewport.height-50;//height/2 + 50;
                this.font.draw(renderer, this.text, xpos, ypos);
            }
        })), 12);
    },

    onDestroyEvent: function() {
        // unregister the event
        me.event.unsubscribe(this.handler);
        me.input.unbindKey(me.input.KEY.ENTER);
        me.input.unbindKey(me.input.KEY.SPACE);
        me.input.unbindPointer(me.input.mouse.LEFT);
        this.ground1 = null;
        this.ground2 = null;
        me.game.world.removeChild(this.logo);
        this.logo = null;
    }
});
<|endoftext|>


<commit_before>/** Markers **/
/**
 * Formats the marker labels.
 * @param {Object} obj - Marker value Object {x:..,y:..}
 * @return {String} Formatted marker string
 */
(function () {

Flotr.defaultMarkerFormatter = function(obj){
  return (Math.round(obj.y*100)/100)+'';
};

Flotr.addType('markers', {
  options: {
    show: false,           // => setting to true will show markers, false will hide
    lineWidth: 1,          // => line width of the rectangle around the marker
    color: '#000000',      // => text color
    fill: false,           // => fill or not the marekers' rectangles
    fillColor: "#FFFFFF",  // => fill color
    fillOpacity: 0.4,      // => fill opacity
    stroke: false,         // => draw the rectangle around the markers
    position: 'ct',        // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
    labelFormatter: Flotr.defaultMarkerFormatter,
    fontSize: Flotr.defaultOptions.fontSize,
    stacked: false,        // => true if markers should be stacked
    stackingType: 'b',     // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
    horizontal: false      // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
  },

  draw : function (options) {

    // TODO construction...
    // TODO test stacked markers.
    this.stack = this.stack || {
        positive : [],
        negative : [],
        values : []
    };

    var
      data            = options.data,
      context         = options.context,
      stack           = options.stacked ? options.stack : false,
      stackType       = options.stackingType,
      stackOffsetNeg,
      stackOffsetPos,
      stackOffset,
      i, x, y, label;

    context.save();
    context.translate(options.offsetLeft, options.offsetTop);
    context.lineJoin = 'round';
    context.lineWidth = options.lineWidth;
    context.strokeStyle = 'rgba(0,0,0,0.5)';
    context.fillStyle = options.fillStyle;

    for (i = 0; i < data.length; ++i) {

      x = data[i][0];
      y = data[i][1];

      if (stack) {
        if (stackType == 'b') {

          function stackPos (a, b) {
            stackOffsetPos = stack.negative[a] || 0;
            stackOffsetNeg = stack.positive[a] || 0;
            if (b > 0) {
              stack.positive[a] = stackOffsetPos + b;
              return stackOffsetPos + b;
            } else {
              stack.negative[a] = stackOffsetNeg + b;
              return stackOffsetNeg + b;
            }
          }

          if (options.horizontal) y = stackPos(y, x);
          else x = stackPos(x, y);

        } else if (stackType == 'a') {
          stackOffset = stack.values[x] || 0;
          stack.values[x] = stackOffset + y;
          y = stackOffset + y;
        }
      }

      label = options.labelFormatter({x: x, y: y, index: i, data : data});
      this.plot(options.xScale(x), options.yScale(y), label, options);
    }
    context.restore();
  },
  plot: function(x, y, label, options) {
    var context = options.context;
    if (isImage(label) && !label.complete) {
      Flotr.EventAdapter.observe(label, 'load', Flotr._.bind(function () {
        context.save();
        context.translate(options.offsetLeft, options.offsetTop);
        this._plot(x, y, label, options);
        context.restore();
      }, this));
    } else {
      this._plot(x, y, label, options);
    }
  },

  _plot: function(x, y, label, options) {
    var context = options.context,
        margin = 2,
        left = x,
        top = y,
        dim;

    if (isImage(label))
      dim = {height : label.height, width: label.width};
    else
      dim = options.text.canvas(label);

    dim.width = Math.floor(dim.width+margin*2);
    dim.height = Math.floor(dim.height+margin*2);

         if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
    else if (options.position.indexOf('l') != -1) left -= dim.width;

         if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
    else if (options.position.indexOf('t') != -1) top -= dim.height;

    left = Math.floor(left)+0.5;
    top = Math.floor(top)+0.5;

    if(options.fill)
      context.fillRect(left, top, dim.width, dim.height);

    if(options.stroke)
      context.strokeRect(left, top, dim.width, dim.height);

    if (isImage(label))
      context.drawImage(label, left+margin, top+margin);
    else
      Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
  }
});

function isImage (i) {
  return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
}

})();
<commit_msg>Make the user load images beforehand.<commit_after>/** Markers **/
/**
 * Formats the marker labels.
 * @param {Object} obj - Marker value Object {x:..,y:..}
 * @return {String} Formatted marker string
 */
(function () {

Flotr.defaultMarkerFormatter = function(obj){
  return (Math.round(obj.y*100)/100)+'';
};

Flotr.addType('markers', {
  options: {
    show: false,           // => setting to true will show markers, false will hide
    lineWidth: 1,          // => line width of the rectangle around the marker
    color: '#000000',      // => text color
    fill: false,           // => fill or not the marekers' rectangles
    fillColor: "#FFFFFF",  // => fill color
    fillOpacity: 0.4,      // => fill opacity
    stroke: false,         // => draw the rectangle around the markers
    position: 'ct',        // => the markers position (vertical align: b, m, t, horizontal align: l, c, r)
    labelFormatter: Flotr.defaultMarkerFormatter,
    fontSize: Flotr.defaultOptions.fontSize,
    stacked: false,        // => true if markers should be stacked
    stackingType: 'b',     // => define staching behavior, (b- bars like, a - area like) (see Issue 125 for details)
    horizontal: false      // => true if markers should be horizontal (For now only in a case on horizontal stacked bars, stacks should be calculated horizontaly)
  },

  draw : function (options) {

    // TODO construction...
    // TODO test stacked markers.
    this.stack = this.stack || {
        positive : [],
        negative : [],
        values : []
    };

    var
      data            = options.data,
      context         = options.context,
      stack           = options.stacked ? options.stack : false,
      stackType       = options.stackingType,
      stackOffsetNeg,
      stackOffsetPos,
      stackOffset,
      i, x, y, label;

    context.save();
    context.translate(options.offsetLeft, options.offsetTop);
    context.lineJoin = 'round';
    context.lineWidth = options.lineWidth;
    context.strokeStyle = 'rgba(0,0,0,0.5)';
    context.fillStyle = options.fillStyle;

    for (i = 0; i < data.length; ++i) {

      x = data[i][0];
      y = data[i][1];

      if (stack) {
        if (stackType == 'b') {

          function stackPos (a, b) {
            stackOffsetPos = stack.negative[a] || 0;
            stackOffsetNeg = stack.positive[a] || 0;
            if (b > 0) {
              stack.positive[a] = stackOffsetPos + b;
              return stackOffsetPos + b;
            } else {
              stack.negative[a] = stackOffsetNeg + b;
              return stackOffsetNeg + b;
            }
          }

          if (options.horizontal) y = stackPos(y, x);
          else x = stackPos(x, y);

        } else if (stackType == 'a') {
          stackOffset = stack.values[x] || 0;
          stack.values[x] = stackOffset + y;
          y = stackOffset + y;
        }
      }

      label = options.labelFormatter({x: x, y: y, index: i, data : data});
      this.plot(options.xScale(x), options.yScale(y), label, options);
    }
    context.restore();
  },
  plot: function(x, y, label, options) {
    var context = options.context;
    if (isImage(label) && !label.complete) {
      throw 'Marker image not loaded.';
    } else {
      this._plot(x, y, label, options);
    }
  },

  _plot: function(x, y, label, options) {
    var context = options.context,
        margin = 2,
        left = x,
        top = y,
        dim;

    if (isImage(label))
      dim = {height : label.height, width: label.width};
    else
      dim = options.text.canvas(label);

    dim.width = Math.floor(dim.width+margin*2);
    dim.height = Math.floor(dim.height+margin*2);

         if (options.position.indexOf('c') != -1) left -= dim.width/2 + margin;
    else if (options.position.indexOf('l') != -1) left -= dim.width;

         if (options.position.indexOf('m') != -1) top -= dim.height/2 + margin;
    else if (options.position.indexOf('t') != -1) top -= dim.height;

    left = Math.floor(left)+0.5;
    top = Math.floor(top)+0.5;

    if(options.fill)
      context.fillRect(left, top, dim.width, dim.height);

    if(options.stroke)
      context.strokeRect(left, top, dim.width, dim.height);

    if (isImage(label))
      context.drawImage(label, left+margin, top+margin);
    else
      Flotr.drawText(context, label, left+margin, top+margin, {textBaseline: 'top', textAlign: 'left', size: options.fontSize, color: options.color});
  }
});

function isImage (i) {
  return typeof i === 'object' && i.constructor && (Image ? true : i.constructor === Image);
}

})();
<|endoftext|>


<commit_before>/* global Backbone, Mail, models */

var views = views || {};

views.DetailedMessage = Backbone.Marionette.ItemView.extend({
	template: "#mail-message-template"
});

views.Message = Backbone.Marionette.ItemView.extend({

	template: "#mail-messages-template",

	ui:{
		iconDelete : '.action.delete',
		star : '.star'
	},

	events: {
		"click .action.delete" : "deleteMessage",
		"click .mail-message-header" : "openMessage",
		"click .star" : "toggleMessageStar"
	},

	onRender: function () {
		// Get rid of that pesky wrapping-div.
		// Assumes 1 child element present in template.
		this.$el = this.$el.children();
		// Unwrap the element to prevent infinitely
		// nesting elements during re-render.
		this.$el.unwrap();
		this.setElement(this.$el);

		var displayName = this.model.get('from');
		_.each(this.$el.find('.avatar'), function(a) {
			$(a).height('32px');
			$(a).imageplaceholder(displayName, displayName);
		});
	},

	toggleMessageStar: function(event) {
		event.stopPropagation();

		var messageId = this.model.id;
		var starred = this.model.get('flags').get('flagged');
		var thisModel = this.model;
		this.ui.star
			.removeClass('icon-starred')
			.removeClass('icon-star')
			.addClass('icon-loading-small');

		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}/toggleStar',
			{
				accountId: Mail.State.currentAccountId,
				folderId: encodeURIComponent(Mail.State.currentFolderId),
				messageId: messageId
			}), {
				data: {
					starred: starred
				},
				type:'POST',
				success: function () {
					thisModel.get('flags').set('flagged', !starred);
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Message could not be starred. Please try again.'));
					thisModel.get('flags').set('flagged', starred);
				}
			});
	},

	openMessage: function(event) {
		event.stopPropagation();
		Mail.UI.openMessage(this.model.id);
	},

	deleteMessage: function(event) {
		event.stopPropagation();
		var thisModel = this.model;
		this.ui.iconDelete.removeClass('icon-delete').addClass('icon-loading');
		this.$el.addClass('transparency').slideUp(function() {
			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index+1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index-1);
			}
			thisModelCollection.remove(thisModel);
			if (Mail.State.currentMessageId === thisModel.id) {
				if (nextMessage) {
					Mail.UI.openMessage(nextMessage.id);
				}
			}
		});

		// really delete the message
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}',
				{
				accountId: Mail.State.currentAccountId,
				folderId: encodeURIComponent(Mail.State.currentFolderId),
				messageId: thisModel.id
			}), {
				data: {},
				type:'DELETE',
				success: function () {
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while deleting message.'));
				}
			});
	}

});

views.Messages = Backbone.Marionette.CompositeView.extend({

	collection: null,

	childView: views.Message,

	childViewContainer: '#mail-message-list',

	currentMessageId: null,

	events: {
		"click #load-new-mail-messages" : "loadNew",
		"click #load-more-mail-messages" : "loadMore"
	},

	template: "#message-list-template",

	initialize: function() {
		this.collection = new models.MessageList();
		this.collection.on('change:flags', this.changeFlags, this);
	},

	changeFlags: function(model) {
		this.trigger('change:flags', model);
	},

	setMessageFlag: function(messageId, flag, val) {
		var message = this.collection.get(messageId);
		if (message) {
			message
				.get('flags')
				.set(flag, val);
		}
	},

	setActiveMessage: function(messageId) {
		// Set active class for current message and remove it from old one

		var message = null;
		if(this.currentMessageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', false);
			}
		}

		this.currentMessageId = messageId;

		if(messageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', true);
			}
		}

	},

	loadNew: function() {
		if (!Mail.State.currentAccountId) {
			return;
		}
		if (!Mail.State.currentFolderId) {
			return;
		}
		// Add loading feedback
		$('#load-new-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Checking mail …'))
			.prop('disabled', true);

		this.loadMessages(true);
	},

	loadMore: function() {
		this.loadMessages(false);
	},

	loadMessages: function(reload) {
		reload = reload || false;
		var from = this.collection.size();
		if (reload){
			from = 0;
		}
		// Add loading feedback
		$('#load-more-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Loading …'))
			.prop('disabled', true);

		var self = this;
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages?from={from}&to={to}',
				{
				'accountId': Mail.State.currentAccountId,
				'folderId':encodeURIComponent(Mail.State.currentFolderId),
				'from': from,
				'to': from + 20
			}), {
				data: {},
				type:'GET',
				success: function (jsondata) {
					if (reload){
						self.collection.reset();
					}
					// Add messages
					self.collection.add(jsondata);

					$('#app-content').removeClass('icon-loading');

					Mail.State.currentMessageId = null;
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while loading messages.'));
					// Set the old folder as being active
					Mail.UI.setFolderActive(Mail.State.currentAccountId, Mail.State.currentFolderId);
				},
				complete: function() {
					// Remove loading feedback again
					$('#load-more-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Load more …'))
						.prop('disabled', false);
					$('#load-new-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Check mail'))
						.prop('disabled', false);
				}
			});
	}
});
<commit_msg>do not unmark active message on refresh fixes #379<commit_after>/* global Backbone, Mail, models */

var views = views || {};

views.DetailedMessage = Backbone.Marionette.ItemView.extend({
	template: "#mail-message-template"
});

views.Message = Backbone.Marionette.ItemView.extend({

	template: "#mail-messages-template",

	ui:{
		iconDelete : '.action.delete',
		star : '.star'
	},

	events: {
		"click .action.delete" : "deleteMessage",
		"click .mail-message-header" : "openMessage",
		"click .star" : "toggleMessageStar"
	},

	onRender: function () {
		// Get rid of that pesky wrapping-div.
		// Assumes 1 child element present in template.
		this.$el = this.$el.children();
		// Unwrap the element to prevent infinitely
		// nesting elements during re-render.
		this.$el.unwrap();
		this.setElement(this.$el);

		var displayName = this.model.get('from');
		_.each(this.$el.find('.avatar'), function(a) {
			$(a).height('32px');
			$(a).imageplaceholder(displayName, displayName);
		});
	},

	toggleMessageStar: function(event) {
		event.stopPropagation();

		var messageId = this.model.id;
		var starred = this.model.get('flags').get('flagged');
		var thisModel = this.model;
		this.ui.star
			.removeClass('icon-starred')
			.removeClass('icon-star')
			.addClass('icon-loading-small');

		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}/toggleStar',
			{
				accountId: Mail.State.currentAccountId,
				folderId: encodeURIComponent(Mail.State.currentFolderId),
				messageId: messageId
			}), {
				data: {
					starred: starred
				},
				type:'POST',
				success: function () {
					thisModel.get('flags').set('flagged', !starred);
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Message could not be starred. Please try again.'));
					thisModel.get('flags').set('flagged', starred);
				}
			});
	},

	openMessage: function(event) {
		event.stopPropagation();
		Mail.UI.openMessage(this.model.id);
	},

	deleteMessage: function(event) {
		event.stopPropagation();
		var thisModel = this.model;
		this.ui.iconDelete.removeClass('icon-delete').addClass('icon-loading');
		this.$el.addClass('transparency').slideUp(function() {
			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index+1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index-1);
			}
			thisModelCollection.remove(thisModel);
			if (Mail.State.currentMessageId === thisModel.id) {
				if (nextMessage) {
					Mail.UI.openMessage(nextMessage.id);
				}
			}
		});

		// really delete the message
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}',
				{
				accountId: Mail.State.currentAccountId,
				folderId: encodeURIComponent(Mail.State.currentFolderId),
				messageId: thisModel.id
			}), {
				data: {},
				type:'DELETE',
				success: function () {
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while deleting message.'));
				}
			});
	}

});

views.Messages = Backbone.Marionette.CompositeView.extend({

	collection: null,

	childView: views.Message,

	childViewContainer: '#mail-message-list',

	currentMessageId: null,

	events: {
		"click #load-new-mail-messages" : "loadNew",
		"click #load-more-mail-messages" : "loadMore"
	},

	template: "#message-list-template",

	initialize: function() {
		this.collection = new models.MessageList();
		this.collection.on('change:flags', this.changeFlags, this);
	},

	changeFlags: function(model) {
		this.trigger('change:flags', model);
	},

	setMessageFlag: function(messageId, flag, val) {
		var message = this.collection.get(messageId);
		if (message) {
			message
				.get('flags')
				.set(flag, val);
		}
	},

	setActiveMessage: function(messageId) {
		// Set active class for current message and remove it from old one

		var message = null;
		if(this.currentMessageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', false);
			}
		}

		this.currentMessageId = messageId;

		if(messageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', true);
			}
		}

	},

	loadNew: function() {
		if (!Mail.State.currentAccountId) {
			return;
		}
		if (!Mail.State.currentFolderId) {
			return;
		}
		// Add loading feedback
		$('#load-new-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Checking mail …'))
			.prop('disabled', true);

		this.loadMessages(true);
	},

	loadMore: function() {
		this.loadMessages(false);
	},

	loadMessages: function(reload) {
		reload = reload || false;
		var from = this.collection.size();
		if (reload){
			from = 0;
		}
		// Add loading feedback
		$('#load-more-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Loading …'))
			.prop('disabled', true);

		var self = this;
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages?from={from}&to={to}',
				{
				'accountId': Mail.State.currentAccountId,
				'folderId':encodeURIComponent(Mail.State.currentFolderId),
				'from': from,
				'to': from + 20
			}), {
				data: {},
				type:'GET',
				success: function (jsondata) {
					if (reload){
						self.collection.reset();
					}
					// Add messages
					self.collection.add(jsondata);

					$('#app-content').removeClass('icon-loading');

					Mail.UI.setMessageActive(Mail.State.currentMessageId);
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while loading messages.'));
					// Set the old folder as being active
					Mail.UI.setFolderActive(Mail.State.currentAccountId, Mail.State.currentFolderId);
				},
				complete: function() {
					// Remove loading feedback again
					$('#load-more-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Load more …'))
						.prop('disabled', false);
					$('#load-new-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Check mail'))
						.prop('disabled', false);
				}
			});
	}
});
<|endoftext|>


<commit_before>/* global Backbone, Marionette, Mail, models, OC */

var views = views || {};

$('.action.delete').tipsy({gravity:'e', live:true});
$('.tipsy-mailto').tipsy({gravity:'n', live:true});

views.Message = Backbone.Marionette.ItemView.extend({

	template: "#mail-messages-template",

	ui:{
		iconDelete : '.action.delete',
		star : '.star'
	},

	events: {
		"click .action.delete" : "deleteMessage",
		"click .mail-message-header" : "openMessage",
		"click .star" : "toggleMessageStar"
	},

	onRender: function () {
		// Get rid of that pesky wrapping-div.
		// Assumes 1 child element present in template.
		this.$el = this.$el.children();
		// Unwrap the element to prevent infinitely
		// nesting elements during re-render.
		this.$el.unwrap();
		this.setElement(this.$el);

		var displayName = this.model.get('from');
		// Don't show any placeholder if 'from' isn't set
		if (!displayName) {
			_.each(this.$el.find('.avatar'), function (a) {
				$(a).height('32px');
				$(a).imageplaceholder(displayName, displayName);
			});
		}
	},

	toggleMessageStar: function(event) {
		event.stopPropagation();

		var starred = this.model.get('flags').get('flagged');

		// directly change star state in the interface for quick feedback
		if(starred) {
			this.ui.star
				.removeClass('icon-starred')
				.addClass('icon-star');
		} else {
			this.ui.star
				.removeClass('icon-star')
				.addClass('icon-starred');
		}
		this.model.flagMessage(
			'flagged',
			!starred
		);
	},

	openMessage: function(event) {
		event.stopPropagation();
		$('#mail-message').removeClass('hidden-mobile');
		Mail.UI.loadMessage(this.model.id, {
			force: true
		});
	},

	deleteMessage: function(event) {
		event.stopPropagation();
		var thisModel = this.model;
		this.ui.iconDelete.removeClass('icon-delete').addClass('icon-loading');
		$('.tipsy').remove();

		thisModel.get('flags').set('unseen', false);

		this.$el.addClass('transparency').slideUp(function() {
			$('.tipsy').remove();
			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index-1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index+1);
			}
			thisModelCollection.remove(thisModel);
			if (Mail.State.currentMessageId === thisModel.id) {
				if (nextMessage) {
					Mail.UI.loadMessage(nextMessage.id);
				}
			}
			// manually trigger mouseover event for current mouse position
			// in order to create a tipsy for the next message if needed
			$(document.elementFromPoint(event.clientX, event.clientY)).trigger('mouseover');
		});

		// really delete the message
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}',
				{
				accountId: Mail.State.currentAccountId,
				folderId: Mail.State.currentFolderId,
				messageId: thisModel.id
			}), {
				data: {},
				type:'DELETE',
				success: function () {
					Mail.Cache.removeMessage(Mail.State.currentAccountId, Mail.State.currentFolderId, thisModel.id);
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while deleting message.'));
				}
			});
	}

});

views.NoSearchResultMessageListView = Marionette.ItemView.extend({
	initialize: function(options) {
		this.model.set('searchTerm', options.filterCriteria.text || "");
	},

	template: "#no-search-results-message-list-template",

	onRender: function() {
		$('#load-more-mail-messages').hide();
	}
});

views.Messages = Backbone.Marionette.CompositeView.extend({

	collection: null,

	childView: views.Message,

	childViewContainer: '#mail-message-list',

	currentMessageId: null,

	events: {
		"click #load-new-mail-messages" : "loadNew",
		"click #load-more-mail-messages" : "loadMore"
	},

	filterCriteria: null,

	template: "#message-list-template",

	initialize: function() {
		this.collection = new models.MessageList();
		this.collection.on('change:flags', this.changeFlags, this);
	},

	getEmptyView: function() {
		if (this.filterCriteria) {
			return views.NoSearchResultMessageListView;
		}
		return views.template;
	},

	emptyViewOptions: function () {
		return { filterCriteria: this.filterCriteria };
	},

	changeFlags: function(model) {
		var unseen = model.get('flags').get('unseen');
		var prevUnseen = model.get('flags')._previousAttributes.unseen;
		//if(_.isUndefined(model._previousAttributes.flags.unseen)) {
		//	prevUnseen = model._previousAttributes.flags.get('unseen');
		//}
		if (unseen !== prevUnseen) {
			this.trigger('change:unseen', model, unseen);
		}
	},

	setMessageFlag: function(messageId, flag, val) {
		var message = this.collection.get(messageId);
		if (message) {
			message.flagMessage(flag, val);
		}
	},

	setActiveMessage: function(messageId) {
		// Set active class for current message and remove it from old one

		var message = null;
		if(this.currentMessageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', false);
			}
		}

		this.currentMessageId = messageId;

		if(messageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', true);
			}
		}

	},

	loadNew: function() {
		if (!Mail.State.currentAccountId) {
			return;
		}
		if (!Mail.State.currentFolderId) {
			return;
		}
		// Add loading feedback
		$('#load-new-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Checking messages …'))
			.prop('disabled', true);

		this.loadMessages(true);
	},

	loadMore: function() {
		this.loadMessages(false);
	},

	filterCurrentMailbox: function(query) {
		this.filterCriteria = {
			text: query
		};
		this.loadNew();
	},

	clearFilter: function() {
		$('#searchbox').val('');
		this.filterCriteria = null;
	},

	loadMessages: function(reload) {
		reload = reload || false;
		var from = this.collection.size();
		if (reload){
			from = 0;
		}
		// Add loading feedback
		$('#load-more-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Loading …'))
			.prop('disabled', true);

		var self = this;
		Mail.Communication.fetchMessageList(
			Mail.State.currentAccountId,
			Mail.State.currentFolderId,
			{
				from: from,
				to: from + 20,
				filter: this.filterCriteria ? this.filterCriteria.text : null,
				force: true,
				// Replace cached message list on reload
				replace: reload,
				onSuccess: function (jsondata) {
					if (reload){
						self.collection.reset();
					}
					// Add messages
					self.collection.add(jsondata);

					$('#app-content').removeClass('icon-loading');

					Mail.UI.setMessageActive(Mail.State.currentMessageId);
				},
				onError: function() {
					Mail.UI.showError(t('mail', 'Error while loading messages.'));
					// Set the old folder as being active
					Mail.UI.setFolderActive(Mail.State.currentAccountId, Mail.State.currentFolderId);
				},
				onComplete: function() {
					// Remove loading feedback again
					$('#load-more-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Load more …'))
						.prop('disabled', false);
					$('#load-new-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Check messages …'))
						.prop('disabled', false);
				}
			});
	}
});
<commit_msg>show sender avatar again.<commit_after>/* global Backbone, Marionette, Mail, models, OC */

var views = views || {};

$('.action.delete').tipsy({gravity:'e', live:true});
$('.tipsy-mailto').tipsy({gravity:'n', live:true});

views.Message = Backbone.Marionette.ItemView.extend({

	template: "#mail-messages-template",

	ui:{
		iconDelete : '.action.delete',
		star : '.star'
	},

	events: {
		"click .action.delete" : "deleteMessage",
		"click .mail-message-header" : "openMessage",
		"click .star" : "toggleMessageStar"
	},

	onRender: function () {
		// Get rid of that pesky wrapping-div.
		// Assumes 1 child element present in template.
		this.$el = this.$el.children();
		// Unwrap the element to prevent infinitely
		// nesting elements during re-render.
		this.$el.unwrap();
		this.setElement(this.$el);

		var displayName = this.model.get('from');
		// Don't show any placeholder if 'from' isn't set
		if (displayName) {
			_.each(this.$el.find('.avatar'), function (a) {
				$(a).height('32px');
				$(a).imageplaceholder(displayName, displayName);
			});
		}
	},

	toggleMessageStar: function(event) {
		event.stopPropagation();

		var starred = this.model.get('flags').get('flagged');

		// directly change star state in the interface for quick feedback
		if(starred) {
			this.ui.star
				.removeClass('icon-starred')
				.addClass('icon-star');
		} else {
			this.ui.star
				.removeClass('icon-star')
				.addClass('icon-starred');
		}
		this.model.flagMessage(
			'flagged',
			!starred
		);
	},

	openMessage: function(event) {
		event.stopPropagation();
		$('#mail-message').removeClass('hidden-mobile');
		Mail.UI.loadMessage(this.model.id, {
			force: true
		});
	},

	deleteMessage: function(event) {
		event.stopPropagation();
		var thisModel = this.model;
		this.ui.iconDelete.removeClass('icon-delete').addClass('icon-loading');
		$('.tipsy').remove();

		thisModel.get('flags').set('unseen', false);

		this.$el.addClass('transparency').slideUp(function() {
			$('.tipsy').remove();
			var thisModelCollection = thisModel.collection;
			var index = thisModelCollection.indexOf(thisModel);
			var nextMessage = thisModelCollection.at(index-1);
			if (!nextMessage) {
				nextMessage = thisModelCollection.at(index+1);
			}
			thisModelCollection.remove(thisModel);
			if (Mail.State.currentMessageId === thisModel.id) {
				if (nextMessage) {
					Mail.UI.loadMessage(nextMessage.id);
				}
			}
			// manually trigger mouseover event for current mouse position
			// in order to create a tipsy for the next message if needed
			$(document.elementFromPoint(event.clientX, event.clientY)).trigger('mouseover');
		});

		// really delete the message
		$.ajax(
			OC.generateUrl('apps/mail/accounts/{accountId}/folders/{folderId}/messages/{messageId}',
				{
				accountId: Mail.State.currentAccountId,
				folderId: Mail.State.currentFolderId,
				messageId: thisModel.id
			}), {
				data: {},
				type:'DELETE',
				success: function () {
					Mail.Cache.removeMessage(Mail.State.currentAccountId, Mail.State.currentFolderId, thisModel.id);
				},
				error: function() {
					Mail.UI.showError(t('mail', 'Error while deleting message.'));
				}
			});
	}

});

views.NoSearchResultMessageListView = Marionette.ItemView.extend({
	initialize: function(options) {
		this.model.set('searchTerm', options.filterCriteria.text || "");
	},

	template: "#no-search-results-message-list-template",

	onRender: function() {
		$('#load-more-mail-messages').hide();
	}
});

views.Messages = Backbone.Marionette.CompositeView.extend({

	collection: null,

	childView: views.Message,

	childViewContainer: '#mail-message-list',

	currentMessageId: null,

	events: {
		"click #load-new-mail-messages" : "loadNew",
		"click #load-more-mail-messages" : "loadMore"
	},

	filterCriteria: null,

	template: "#message-list-template",

	initialize: function() {
		this.collection = new models.MessageList();
		this.collection.on('change:flags', this.changeFlags, this);
	},

	getEmptyView: function() {
		if (this.filterCriteria) {
			return views.NoSearchResultMessageListView;
		}
		return views.template;
	},

	emptyViewOptions: function () {
		return { filterCriteria: this.filterCriteria };
	},

	changeFlags: function(model) {
		var unseen = model.get('flags').get('unseen');
		var prevUnseen = model.get('flags')._previousAttributes.unseen;
		//if(_.isUndefined(model._previousAttributes.flags.unseen)) {
		//	prevUnseen = model._previousAttributes.flags.get('unseen');
		//}
		if (unseen !== prevUnseen) {
			this.trigger('change:unseen', model, unseen);
		}
	},

	setMessageFlag: function(messageId, flag, val) {
		var message = this.collection.get(messageId);
		if (message) {
			message.flagMessage(flag, val);
		}
	},

	setActiveMessage: function(messageId) {
		// Set active class for current message and remove it from old one

		var message = null;
		if(this.currentMessageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', false);
			}
		}

		this.currentMessageId = messageId;

		if(messageId !== null) {
			message = this.collection.get(this.currentMessageId);
			if (message) {
				message.set('active', true);
			}
		}

	},

	loadNew: function() {
		if (!Mail.State.currentAccountId) {
			return;
		}
		if (!Mail.State.currentFolderId) {
			return;
		}
		// Add loading feedback
		$('#load-new-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Checking messages …'))
			.prop('disabled', true);

		this.loadMessages(true);
	},

	loadMore: function() {
		this.loadMessages(false);
	},

	filterCurrentMailbox: function(query) {
		this.filterCriteria = {
			text: query
		};
		this.loadNew();
	},

	clearFilter: function() {
		$('#searchbox').val('');
		this.filterCriteria = null;
	},

	loadMessages: function(reload) {
		reload = reload || false;
		var from = this.collection.size();
		if (reload){
			from = 0;
		}
		// Add loading feedback
		$('#load-more-mail-messages')
			.addClass('icon-loading-small')
			.val(t('mail', 'Loading …'))
			.prop('disabled', true);

		var self = this;
		Mail.Communication.fetchMessageList(
			Mail.State.currentAccountId,
			Mail.State.currentFolderId,
			{
				from: from,
				to: from + 20,
				filter: this.filterCriteria ? this.filterCriteria.text : null,
				force: true,
				// Replace cached message list on reload
				replace: reload,
				onSuccess: function (jsondata) {
					if (reload){
						self.collection.reset();
					}
					// Add messages
					self.collection.add(jsondata);

					$('#app-content').removeClass('icon-loading');

					Mail.UI.setMessageActive(Mail.State.currentMessageId);
				},
				onError: function() {
					Mail.UI.showError(t('mail', 'Error while loading messages.'));
					// Set the old folder as being active
					Mail.UI.setFolderActive(Mail.State.currentAccountId, Mail.State.currentFolderId);
				},
				onComplete: function() {
					// Remove loading feedback again
					$('#load-more-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Load more …'))
						.prop('disabled', false);
					$('#load-new-mail-messages')
						.removeClass('icon-loading-small')
						.val(t('mail', 'Check messages …'))
						.prop('disabled', false);
				}
			});
	}
});
<|endoftext|>


<commit_before>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Responsive presentation and behavior for HTML data panels
//>>label: Panel
//>>group: Widgets
//>>css.structure: ../css/structure/jquery.mobile.panel.css
//>>css.theme: ../css/themes/default/jquery.mobile.theme.css

define( [ "jquery", "../jquery.mobile.widget", "./page", "./page.sections" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function( $, undefined ) {

$.widget( "mobile.panel", $.mobile.widget, {
	options: {
		classes: {
			panel: "ui-panel"
		},
		theme: null,
		link: null,
		position: "left",
		dismissible: true,
		display: "pan",
		initSelector: ":jqmData(role='panel')"
	},
	_handleLink: function( roleType , callback ){
		var elId = this.element.attr( "id" ),
			defaultOptions = this.options;
		$( document ).bind( "pagebeforechange", function( e, data ) {
			var $link, id;
			if ( data.options.role === roleType ) {
				e.preventDefault();
				$link = data.options.link;
				id = $link.attr( "href" ).split( "#" )[1];
				if( elId === id ){
					callback( $link , id , defaultOptions );
				}
				return false;
			}
		});
	},
	_blockPage: function( clickable , position ){
		var deferred = $.Deferred();
		var $div = $( "<div>" ),
			$panel = this,
			slideDir = position === "left" ? "right" : "left",
			klass = clickable ? "ui-panel-dismiss" : "ui-panel-no-dismiss";
		setTimeout(function(){
			$div.css( "width" , "70%" )
				.css( "height" , $.mobile.activePage.height() )
				.css( "position" , "absolute" )
				.css( "top" , 0 )
				.css( slideDir , 0 )
				.attr( "id" , "page-block" )
				.addClass( klass )
				.appendTo( $.mobile.activePage );
			if( clickable ){
				$div.bind( "vclick" , function(){
					$panel.close();
				});
			}
			deferred.resolve();
		}, 0); // TODO get rid of setTimeout 0 hacks
		return deferred.promise();
	},
	_create: function() {
		var o = this.options,
			klass = o.classes.panel,
			$el = this.element,
			$closeLink = $el.find( "[data-rel=close]" );
		$el.addClass( klass );
		$el.parents( "[data-role=page]" ).addClass( "panel-page" );
		if( o.theme ){
			$el.addClass( "ui-body-" + o.theme );
		}
		this._handleLink( "panel" , function( $link , id , options ){
			var o = {
				position: $link.jqmData( "position" ),
				dismissible: $link.jqmData( "dismissible" ),
				display: $link.jqmData( "display" )
			};
			for( var i in o ){
				if( o.hasOwnProperty( i ) && typeof o[ i ] !== "undefined" ){
					options[ i ] = o[ i ];
				}
			}
			$( "#" + id ).panel( "toggle" , {
				position: options.position,//left right
				dismissible: options.dismissible,//true or false
				display: options.display,// overlay or push
				link: $link
			});
		});
		$closeLink.on( "vclick" , function( e ){
			e.preventDefault();
			$el.panel( "close" );
			return false;
		});
		this._trigger( "create" );
	},
	_position: function( options ){
		var deferred = $.Deferred();
		var o = options,
			klass = o.classes.panel,
			$el = this.element;
		setTimeout(function(){
			$el.addClass( "hidden" )
				.addClass( klass + "-position-" + o.position )
				.addClass( klass + "-dismissible-" + o.dismissible )
				.addClass( klass + "-display-" + o.display )
				.jqmData( "position" , o.position )
				.jqmData( "display" , o.display )
				.jqmData( "dismissible" , o.dismissible )
				.removeClass( "hidden" );
				deferred.resolve( options );
		}, 0); // TODO get rid of setTimeout 0 hacks
		return deferred.promise();
	},
	_destroy: function(){},
	open: function( options , toggle ){
		var self = this;
		var deferred = $.Deferred();
		var o = this.options,
			klass = o.classes.panel,
			$el = this.element;
		for( var i in options ){
			if( options.hasOwnProperty( i ) ){
				o[ i ] = options[ i ];
			}
		}
		this._position( o )
		.then( function(){
			return self._blockPage( o.dismissible , o.position );
		})
		.then( function(){
			$el.addClass( klass + "-active" );
			if( o.display === "pan" ){
				$( ".ui-content, .ui-header, .ui-footer" ).addClass( "panel-shift-" + o.position );
			}
			self._trigger( "open" , "open" , { link: o.link } );
			deferred.resolve();
		});
		return deferred.promise();
	},
	close: function( options , toggle ){
		var deferred = $.Deferred();
		var o = this.options,
			klass = o.classes.panel,
			$el = this.element,
			position = $el.jqmData( "position" ),
			display = $el.jqmData( "display" ),
			dismissible = $el.jqmData( "dismissible" );
		for( var i in options ){
			if( options.hasOwnProperty( i ) ){
				o[ i ] = options[ i ];
			}
		}
		if( toggle ){
			$el.addClass( "ui-panel-toggle" );
		}

		$el.one( "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd" , function(){
			var $this = $( this );
			$this.removeClass( klass + "-position-" + position )
				.removeClass( klass + "-display-" + display )
				.removeClass( klass + "-dismissible-" + dismissible );
			$this.data( "mobile-panel" )._trigger( "close" , "close" , { link: o.link } );
			deferred.resolve( o , toggle );
		});

		$el.removeClass( klass + "-active" );
		$( "#page-block" ).remove();
		$( ".ui-content, .ui-header, .ui-footer" ).removeClass( "panel-shift-" + position );
		return deferred.promise();
	},
	toggle: function( options ){
		var $el = this.element,
			active = $( ".ui-panel-active" ).data( "mobile-panel" ),
			self = this;
		if( active &&
				( active.element.jqmData( "position") === options.position ) &&
				( active.element.attr( "id" ) === $el.attr( "id" ) ) &&
				( active.element.jqmData( "display" ) === options.display ) ){
			return active.close( options );
		} else if ( active ){
			active.close( options , true ).
			then( function( options , toggle ){
				self.open( options , toggle );
			});
		} else {
			return this.open( options );
		}
	},
	refresh: function(){
	}
});

$( document ).bind( "panelopen panelclose" , function( e , data ){
	var $link = data.link, $parent;
	if( $link ){
		$parent = $link.parent().parent();
		if ($parent.hasClass("ui-li")) {
			$link = $parent.parent();
		}
		$link.removeClass( $.mobile.activeBtnClass );
	}
});

//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ) {
	$.mobile.panel.prototype.enhanceWithin( e.target );
});

})( jQuery );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
});
//>>excludeEnd("jqmBuildExclude");

<commit_msg>Making sure that I'm not ever modifying this.options by making shallow clones of it<commit_after>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Responsive presentation and behavior for HTML data panels
//>>label: Panel
//>>group: Widgets
//>>css.structure: ../css/structure/jquery.mobile.panel.css
//>>css.theme: ../css/themes/default/jquery.mobile.theme.css

define( [ "jquery", "../jquery.mobile.widget", "./page", "./page.sections" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function( $, undefined ) {

$.widget( "mobile.panel", $.mobile.widget, {
	options: {
		classes: {
			panel: "ui-panel"
		},
		theme: null,
		link: null,
		position: "left",
		dismissible: true,
		display: "pan",
		initSelector: ":jqmData(role='panel')"
	},
	_handleLink: function( roleType , callback ){
		var elId = this.element.attr( "id" ),
			self = this;
		$( document ).bind( "pagebeforechange", function( e, data ) {
			var $link, id;
			if ( data.options.role === roleType ) {
				e.preventDefault();
				$link = data.options.link;
				id = $link.attr( "href" ).split( "#" )[1];
				if( elId === id ){
					callback.call( self , $link , id );
				}
				return false;
			}
		});
	},
	_blockPage: function( clickable , position ){
		var deferred = $.Deferred();
		var $div = $( "<div>" ),
			$panel = this,
			slideDir = position === "left" ? "right" : "left",
			klass = clickable ? "ui-panel-dismiss" : "ui-panel-no-dismiss";
		setTimeout(function(){
			$div.css( "width" , "70%" )
				.css( "height" , $.mobile.activePage.height() )
				.css( "position" , "absolute" )
				.css( "top" , 0 )
				.css( slideDir , 0 )
				.attr( "id" , "page-block" )
				.addClass( klass )
				.appendTo( $.mobile.activePage );
			if( clickable ){
				$div.bind( "vclick" , function(){
					$panel.close();
				});
			}
			deferred.resolve();
		}, 0); // TODO get rid of setTimeout 0 hacks
		return deferred.promise();
	},
	_create: function() {
		var o = this.options,
			klass = o.classes.panel,
			$el = this.element,
			$closeLink = $el.find( "[data-rel=close]" );
		$el.addClass( klass );
		$el.parents( "[data-role=page]" ).addClass( "panel-page" );
		if( o.theme ){
			$el.addClass( "ui-body-" + o.theme );
		}
		this._handleLink( "panel" , function( $link , id ){
			var options = $.extend( {} , this.options ),
				op = {
					position: $link.jqmData( "position" ),
					dismissible: $link.jqmData( "dismissible" ),
					display: $link.jqmData( "display" )
				};
			for( var i in op ){
				if( op.hasOwnProperty( i ) && typeof op[ i ] !== "undefined" ){
					options[ i ] = op[ i ];
				}
			}
			$( "#" + id ).panel( "toggle" , {
				position: options.position,//left right
				dismissible: options.dismissible,//true or false
				display: options.display,// overlay or push
				link: $link
			});
		});
		$closeLink.on( "vclick" , function( e ){
			e.preventDefault();
			$el.panel( "close" );
			return false;
		});
		this._trigger( "create" );
	},
	_position: function( options ){
		var deferred = $.Deferred();
		var o = options,
			klass = o.classes.panel,
			$el = this.element;
		setTimeout(function(){
			$el.addClass( "hidden" )
				.addClass( klass + "-position-" + o.position )
				.addClass( klass + "-dismissible-" + o.dismissible )
				.addClass( klass + "-display-" + o.display )
				.jqmData( "position" , o.position )
				.jqmData( "display" , o.display )
				.jqmData( "dismissible" , o.dismissible )
				.removeClass( "hidden" );
				deferred.resolve( options );
		}, 0); // TODO get rid of setTimeout 0 hacks
		return deferred.promise();
	},
	_destroy: function(){},
	open: function( options , toggle ){
		var self = this;
		var deferred = $.Deferred();
		var o = $.extend( {} , this.options ),
			klass = o.classes.panel,
			$el = this.element;
		for( var i in options ){
			if( options.hasOwnProperty( i ) ){
				o[ i ] = options[ i ];
			}
		}
		this._position( o )
		.then( function(){
			return self._blockPage( o.dismissible , o.position );
		})
		.then( function(){
			$el.one( "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd" , function(){
				self._trigger( "open" , "open" , { link: o.link } );
				deferred.resolve();
			});
			$el.addClass( klass + "-active" );
			if( o.display === "pan" ){
				$( ".ui-content, .ui-header, .ui-footer" ).addClass( "panel-shift-" + o.position );
			}
		});
		return deferred.promise();
	},
	close: function( options , toggle ){
		var deferred = $.Deferred();
		var o = $.extend( {} , this.options ),
			klass = o.classes.panel,
			$el = this.element,
			position = $el.jqmData( "position" ),
			display = $el.jqmData( "display" ),
			dismissible = $el.jqmData( "dismissible" );
		for( var i in options ){
			if( options.hasOwnProperty( i ) ){
				o[ i ] = options[ i ];
			}
		}
		if( toggle ){
			$el.addClass( "ui-panel-toggle" );
		}

		$el.one( "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd" , function(){
			var $this = $( this );
			$this.removeClass( klass + "-position-" + position )
				.removeClass( klass + "-display-" + display )
				.removeClass( klass + "-dismissible-" + dismissible );
			$this.data( "mobile-panel" )._trigger( "close" , "close" , { link: o.link } );
			deferred.resolve( o , toggle );
		});

		$el.removeClass( klass + "-active" );
		$( "#page-block" ).remove();
		$( ".ui-content, .ui-header, .ui-footer" ).removeClass( "panel-shift-" + position );
		return deferred.promise();
	},
	toggle: function( options ){
		var $el = this.element,
			active = $( ".ui-panel-active" ).data( "mobile-panel" ),
			self = this;
		if( active &&
				( active.element.jqmData( "position") === options.position ) &&
				( active.element.attr( "id" ) === $el.attr( "id" ) ) &&
				( active.element.jqmData( "display" ) === options.display ) ){
			return active.close( options );
		} else if ( active ){
			active.close( options , true ).
			then( function( options , toggle ){
				self.open( options , toggle );
			});
		} else {
			return this.open( options );
		}
	},
	refresh: function(){
	}
});

$( document ).bind( "panelopen panelclose" , function( e , data ){
	var $link = data.link, $parent;
	if( $link ){
		$parent = $link.parent().parent();
		if ($parent.hasClass("ui-li")) {
			$link = $parent.parent();
		}
		$link.removeClass( $.mobile.activeBtnClass );
	}
});

//auto self-init widgets
$( document ).bind( "pagecreate create", function( e ) {
	$.mobile.panel.prototype.enhanceWithin( e.target );
});

})( jQuery );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
});
//>>excludeEnd("jqmBuildExclude");

<|endoftext|>


<commit_before>const tabListener        = require('binary-style').tabListener;
const Url                = require('./url');
const applyToAllElements = require('./utility').applyToAllElements;

const TabSelector = (() => {
    const obj_tabs = {};

    const onLoad = () => {
        tabListener();
        applyToAllElements('.tab-selector-wrapper .tm-ul', (tab_selector) => {
            const tab_selector_id = tab_selector.getAttribute('id');
            applyToAllElements('.tm-li', (tab) => {
                if (!/tab-selector/.test(tab.className)) {
                    const tab_id = tab.getAttribute('id');
                    if (!obj_tabs[tab_selector_id]) {
                        obj_tabs[tab_selector_id] = { el_selected_tab: tab, id_tabs: [] };
                    }
                    obj_tabs[tab_selector_id].id_tabs.push(tab_id);
                }
            }, '', tab_selector);
        });
        // set initial width and margin-left of tab selector
        repositionSelector();
        window.addEventListener('resize', repositionSelector);

        applyToAllElements('.tm-li', (element) => {
            element.addEventListener('click', slideSelectorOnMenuClick);
        });

        applyToAllElements('.go-left', (element) => {
            element.addEventListener('click', goLeft);
        });
        applyToAllElements('.go-right', (element) => {
            element.addEventListener('click', goRight);
        });
    };

    const repositionSelector = () => {
        Object.keys(obj_tabs).forEach((tab_id) => {
            changeTab(undefined, undefined, tab_id);
        });
    };

    const slideSelectorOnMenuClick = (e) => {
        if (e.target.nodeName !== 'A' || /a-active/.test(e.target.classList)) {
            return;
        }
        const selector = e.target.closest('ul').getAttribute('id');
        slideSelector(selector, e.target);
        updateURL(selector, e.target.parentNode.getAttribute('id'));
    };

    const updateURL = (selector, tab_id) => {
        const params_hash = Url.paramsHash();
        params_hash[selector] = tab_id;
        const updated_url = `${window.location.origin}${window.location.pathname}?${Url.paramsHashToString(params_hash)}`;
        window.history.replaceState({ url: updated_url }, null, updated_url);
    };

    const goLeft = (e) => {
        changeTab(e, true);
    };

    const goRight = (e) => {
        changeTab(e, false);
    };

    const changeTab = (e, go_left, selector_id) => {
        let selector = selector_id || e.target.getAttribute('data-parent');
        let el_parent,
            el_to_show_from_hash;
        const params_hash = Url.paramsHash();
        if (obj_tabs[selector].id_tabs.indexOf(params_hash[selector]) > -1) {
            el_to_show_from_hash = document.getElementById(params_hash[selector]);
            if (el_to_show_from_hash) {
                el_parent = el_to_show_from_hash.parentNode;
                selector  = el_parent.getAttribute('id');
            }
        } else {
            el_parent = document.getElementById(selector);
        }
        if (!el_parent) {
            return;
        }
        if (typeof go_left === 'undefined' && !el_to_show_from_hash) {
            slideSelector(selector, obj_tabs[selector].el_selected_tab);
            return;
        }
        const elements = el_parent.getElementsByTagName('li');
        for (let i = 0; i < elements.length - 1; i++) {
            if (/active/.test(elements[i].classList)) {
                let index_to_show = 0;
                let el_to_show;
                if (params_hash[selector] && selector_id) {
                    el_to_show = el_to_show_from_hash;
                } else {
                    if (go_left) {
                        index_to_show = elements[i - 1] ? i - 1 : elements.length - 2;
                    } else {
                        index_to_show = i + 1 !== elements.length - 1 && elements[i + 1] ? i + 1 : 0;
                    }
                    el_to_show = elements[index_to_show];
                }
                obj_tabs[selector].el_selected_tab = el_to_show;
                if (!selector_id) {
                    updateURL(selector, el_to_show.getAttribute('id'));
                }

                selectCircle(selector, i, index_to_show);
                slideSelector(selector, el_to_show);
                elements[i].classList.remove('active');
                document.getElementById(`${elements[i].getAttribute('id')}-content`).classList.add('invisible');
                el_to_show.classList.add('active');
                document.getElementById(`${el_to_show.getAttribute('id')}-content`).classList.remove('invisible');

                if (params_hash.section) {
                    setTimeout(() => { $.scrollTo($(`#${params_hash.section}`), 500, { offset: -10 }); }, 500);
                }

                break;
            }
        }
    };

    const slideSelector = (selector, el_to_show) => {
        document.getElementById(`${selector}_selector`).setAttribute('style', `width: ${el_to_show.offsetWidth}px; margin-left: ${el_to_show.offsetLeft}px;`);
    };

    const selectCircle = (selector, old_index, index_to_show) => {
        const el_circle = document.getElementById(`${selector}_circles`);
        if (el_circle) {
            const all_circles = el_circle.children;
            all_circles[old_index].classList.remove('selected');
            all_circles[index_to_show].classList.add('selected');
        }
    };

    const onUnload = () => {
        window.removeEventListener('resize', repositionSelector);

        applyToAllElements('.tm-li', (element) => {
            element.removeEventListener('click', slideSelectorOnMenuClick);
        });

        applyToAllElements('.go-left', (element) => {
            element.removeEventListener('click', goLeft);
        });
        applyToAllElements('.go-right', (element) => {
            element.removeEventListener('click', goRight);
        });
    };

    return {
        onLoad,
        onUnload,
    };
})();

module.exports = TabSelector;
<commit_msg>refactor code, no need to get list of tabs from DOM<commit_after>const tabListener        = require('binary-style').tabListener;
const Url                = require('./url');
const applyToAllElements = require('./utility').applyToAllElements;

const TabSelector = (() => {
    // obj_tabs will be built in the following format:
    // obj_tabs = { first_tab_group_selector_id: { id_tabs: [ id_of_tab_one, id_of_tab_two ] }
    // we will use id_tabs to handle which tab to show when going to the left or right tab
    const obj_tabs = {};

    const onLoad = () => {
        tabListener();
        applyToAllElements('.tab-selector-wrapper .tm-ul', (tab_selector) => {
            const tab_selector_id = tab_selector.getAttribute('id');
            applyToAllElements('.tm-li', (tab) => {
                if (!/tab-selector/.test(tab.className)) {
                    const tab_id = tab.getAttribute('id');
                    if (!obj_tabs[tab_selector_id]) {
                        obj_tabs[tab_selector_id] = { id_tabs: [] };
                    }
                    obj_tabs[tab_selector_id].id_tabs.push(tab_id);
                }
            }, '', tab_selector);
        });
        // set initial width and margin-left of tab selector
        repositionSelector();
        window.addEventListener('resize', repositionSelector);

        applyToAllElements('.tm-li', (element) => {
            element.addEventListener('click', slideSelectorOnMenuClick);
        });

        applyToAllElements('.go-left', (element) => {
            element.addEventListener('click', goLeft);
        });
        applyToAllElements('.go-right', (element) => {
            element.addEventListener('click', goRight);
        });
    };

    const repositionSelector = () => {
        const params_hash = Url.paramsHash();
        Object.keys(obj_tabs).forEach((tab_id) => {
            const id_to_show = params_hash[tab_id] || obj_tabs[tab_id].id_tabs[0];
            const el_to_show = document.getElementById(id_to_show);
            const selector   = el_to_show.parentNode.getAttribute('id');
            changeTab({ selector, el_to_show });
        });
    };

    const slideSelectorOnMenuClick = (e) => {
        if (e.target.nodeName !== 'A' || /a-active/.test(e.target.classList)) {
            return;
        }
        const selector = e.target.closest('ul').getAttribute('id');
        slideSelector(selector, e.target);
        updateURL(selector, e.target.parentNode.getAttribute('id'));
    };

    const updateURL = (selector, tab_id) => {
        const params_hash = Url.paramsHash();
        params_hash[selector] = tab_id;
        const updated_url = `${window.location.origin}${window.location.pathname}?${Url.paramsHashToString(params_hash)}`;
        window.history.replaceState({ url: updated_url }, null, updated_url);
    };

    const goLeft = (e) => {
        changeTab({ selector: e.target.getAttribute('data-parent'), direction: 'left' });
    };

    const goRight = (e) => {
        changeTab({ selector: e.target.getAttribute('data-parent'), direction: 'right' });
    };

    const changeTab = (options) => {
        const params_hash     = Url.paramsHash();
        const arr_id_tabs     = obj_tabs[options.selector].id_tabs;
        const id_selected_tab = params_hash[options.selector] || obj_tabs[options.selector].id_tabs[0];
        const current_index   = arr_id_tabs.indexOf(id_selected_tab);
        let index_to_show = current_index;
        if (options.direction) {
            if (options.direction === 'left') {
                index_to_show = current_index > 0 ? current_index - 1 : arr_id_tabs.length - 1;
            } else {
                index_to_show = current_index === arr_id_tabs.length - 1 ? 0 : current_index + 1;
            }
            options.el_to_show = document.getElementById(arr_id_tabs[index_to_show]);
            updateURL(options.selector, options.el_to_show.getAttribute('id'));
        }

        if (!options.el_to_show || !options.selector) {
            return;
        }

        selectCircle(options.selector, current_index, index_to_show);
        slideSelector(options.selector, options.el_to_show);
        showContent(options.selector, document.getElementById(id_selected_tab), options.el_to_show);

        if (params_hash.section) {
            setTimeout(() => { $.scrollTo($(`#${params_hash.section}`), 500, { offset: -10 }); }, 500);
        }
    };

    const slideSelector = (selector, el_to_show) => {
        document.getElementById(`${selector}_selector`).setAttribute('style', `width: ${el_to_show.offsetWidth}px; margin-left: ${el_to_show.offsetLeft}px;`);
    };

    const selectCircle = (selector, old_index, index_to_show) => {
        const el_circle = document.getElementById(`${selector}_circles`);
        if (el_circle) {
            const all_circles = el_circle.children;
            all_circles[old_index].classList.remove('selected');
            all_circles[index_to_show].classList.add('selected');
        }
    };

    const showContent = (selector, old_content, new_content) => {
        old_content.classList.remove('active');
        document.getElementById(`${old_content.getAttribute('id')}-content`).classList.add('invisible');
        new_content.classList.add('active');
        document.getElementById(`${new_content.getAttribute('id')}-content`).classList.remove('invisible');
    };

    const onUnload = () => {
        window.removeEventListener('resize', repositionSelector);

        applyToAllElements('.tm-li', (element) => {
            element.removeEventListener('click', slideSelectorOnMenuClick);
        });

        applyToAllElements('.go-left', (element) => {
            element.removeEventListener('click', goLeft);
        });
        applyToAllElements('.go-right', (element) => {
            element.removeEventListener('click', goRight);
        });
    };

    return {
        onLoad,
        onUnload,
    };
})();

module.exports = TabSelector;
<|endoftext|>


<commit_before>/**
 * Controls the First Time Experience for the user interface.
 *
 */
betterlink_user_interface.createModule("FTE", function(api, apiInternal) {
	api.requireModules( ["Util.DOM", "Storage", "FTE Tooltip", "LastSubmission", "Drawer Slider", "Action Drawer", "Util.HTTP", "HTTP", "Smooth Scrolling", "Event Messaging"] );

	// Element Selectors. These should be dynamic to make the FTE
	// resilient.
	var TOOLTIP_SELECTOR = "#betterlink-tooltip";
	var DRAWER_ID = "betterlink-drawer";
	var NEXUS_CLASS = "betterlink-nexus";
	var PEN_CLASS = "betterlink-pen";
	var SELECTED_CLASS = "betterlink-selected";
	var RESULT_CLASS = api["config"]["highlightCustomCssClass"] || api["config"]["highlightCssClass"];
	var DRAWER_BACKGROUND = "#E9E9E9";

	var BUTTON_CLASS = "betterlink-tooltip-btn";
	var BUTTON_CLASS_SECONDARY = "betterlink-tooltip-btn-secondary";
	var BUTTONS_CLASS = "betterlink-tooltip-btns";
	var MASK_CLASS = "betterlink-fte-mask";
	var MASK_OVERRIDE_CLASS = "betterlink-fte-mask-override";

	var CSS =
		[   TOOLTIP_SELECTOR + " ." + BUTTON_CLASS + " { background-color: #3299BB; border: 1px solid #277799; color: #FFF; font-size: 14px; margin: 5px; padding: 6px 12px; border-radius: .4em; display: inline-block; text-align: center; white-space: nowrap; vertical-align: middle; touch-action: manipulation; cursor: pointer; text-decoration: none; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS + ":hover { background-color: #277799; text-decoration: none; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS_SECONDARY + " { background-color: #BCBCBC; border: 1px solid #9B9B9B; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS_SECONDARY + ":hover { background-color: #9B9B9B; }",
			TOOLTIP_SELECTOR + " ." + BUTTONS_CLASS + " { text-align: right; width: auto; margin-top: 5px }",
			TOOLTIP_SELECTOR + " p { margin-bottom: 6px; }",

			// The mask has a z-index one less than the maximum
			// Any overrides have a z-index that's the maximum value
			"." + MASK_CLASS + ":after { content: ''; background-color: #000; opacity: 0.6; display: block; position: fixed; top: 0; left: 0; height: 100%; width: 100%; z-index: 2147483646; }",
			"." + MASK_OVERRIDE_CLASS + "{ position: relative !important; box-shadow: 0 0 5px 2px #BCBCBC; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); }"

		].join(' ');

	var stylesInitialized = false;
	var fteRun = false;
	var drawerLock;

	/****************************************************************************************************/

	apiInternal.events.registerObserverForRemoveBetterlink(closeFTE);
	apiInternal.addInitListener(loadFTE);

	// Register the FTE to run with the next successful submission
	function loadFTE() {
		if(api['config']['enableFTE']) {
			// Queries the server to determine if the user has already
			// viewed the FTE, based on registerFTE()
			apiInternal.checkFTE(decideToLoadFTE);

			// Everytime there is a new submission, refresh that the user
			// doesn't need the FTE. In the event the user *has not* seen
			// it, this will get called along with runIntro the first time.
			apiInternal.lastSubmission.subscribeSuccess.onsuccess(apiInternal.registerFTE);

			// If the user is opening the page and the 'fromFTE' cookie is
			// set, assume the user has just clicked 'Follow Your Link'
			// from the FTE. Once Betterlink highlights the saved content,
			// display the continuation of the FTE.
			if(apiInternal.storage.getCookie('betterlink-fromFTE')) {
				window.setTimeout(runViewingLink, 1000);
			}
		}
	}

	// Asynchronous check for whether we should load the FTE for this
	// user
	function decideToLoadFTE(errorInfo, response) {
		// If we can't get a response from the server, assume the user
		// *has not* seen the FTE. Rationale: we're currently only
		// displaying the FTE on the Betterlink landing page.
		if(errorInfo) {
			apiInternal.lastSubmission.subscribeSuccess.onsuccess(runIntro);
		}
		else {
			// Asynchronously parse the response JSON. Expected result:
			// { fte: '1' } indicates the user has seen the FTE
			apiInternal.util.http.parseJSONServerResponseAsync(response, function(parsedResponse) {
				if(Object.hasOwnProperty.call(parsedResponse, 'fte')) {
					if(parsedResponse['fte'] !== '1') {
						apiInternal.lastSubmission.subscribeSuccess.onsuccess(runIntro);
					}
				}
			});
		}
	}

	// STEP 1
	// This explains that a new link was created and allows the user
	// to demo visiting their new link
	function runIntro() {
		if(!fteRun) {
			// The core interface gets loaded via an init listener, just
			// like loadFTE(). This means we can't guarantee that the
			// drawer won't load *after* we attempt to subscribe the
			// onSuccess event listener. Therefore, we have to run the
			// initialization check here.
			if(!apiInternal.drawer.initialized) {
				fteRun = true;
				return;
			}

			fteRun = true;
			initializeStyles();

			var nexus = apiInternal.util.dom.getElementsByClassName(NEXUS_CLASS)[0];
			var tooltipContent = buildIntroTooltip();

			var drawer = document.getElementById(DRAWER_ID);
			drawerLock = apiInternal.slider.lockDrawerOpen();
			apiInternal.util.dom.applyClassToElement(drawer, MASK_CLASS);

			applyMaskOverrides(nexus, DRAWER_BACKGROUND);
			apiInternal.fteTooltip.addTooltipToDrawer(nexus, tooltipContent);
		}
	}

	// STEP 2
	// This highlights the next steps that are available to the user.
	// Specifically, sharing or saving the link.
	function runActionElements() {
		var pen = apiInternal.util.dom.getElementsByClassName(PEN_CLASS)[0];
		var tooltipContent = buildActionTooltip();

		removeMaskOverrides();
		applyMaskOverrides(pen, DRAWER_BACKGROUND);
		apiInternal.fteTooltip.addTooltipToDrawer(pen, tooltipContent);
	}

	// STEP 3
	// This communicates that the user can click on the previous submission
	// text to re-open the drawer
	function runReopenDrawer() {
		var selectedText = apiInternal.util.dom.getElementsByClassName(SELECTED_CLASS);
		if(selectedText && selectedText.length) {
			var firstSelectedText = selectedText[0];
			var tooltipContent = buildReopenTooltip();

			removeMaskOverrides();
			applyMaskOverrides(selectedText);
			apiInternal.fteTooltip.addTooltipToPage(firstSelectedText, tooltipContent);

			apiInternal.smoothScroll(TOOLTIP_SELECTOR.substr(1), {pixelBuffer: 125});
		}
	}

	// STEP 1b
	// This explains that the user is viewing the result of their submission
	// and directs the user back to the previous page.
	// Note: This step is the first of a newly-loaded page
	function runViewingLink() {
		removeFTECookie();
		var selectedText = apiInternal.util.dom.getElementsByClassName(RESULT_CLASS);
		if(selectedText && selectedText.length) {
			initializeStyles();
			var firstSelectedText = selectedText[0];
			var tooltipContent = buildViewingLinkTooltip();

			// Because the drawer will be closed in this example, we need
			// to apply the overlay to the body
			apiInternal.util.dom.applyClassToElement(document.body, MASK_CLASS);

			applyMaskOverrides(selectedText);
			apiInternal.fteTooltip.addTooltipToPage(firstSelectedText, tooltipContent);

			apiInternal.smoothScroll(TOOLTIP_SELECTOR.substr(1), {pixelBuffer: 75});
		}
	}

	function closeFTE(e) {
		if(e) {
			e.preventDefault ? e.preventDefault() : window.event.returnValue = false;
		}

		var drawer = document.getElementById(DRAWER_ID);
		apiInternal.fteTooltip.remove();
		removeMaskOverrides();
		apiInternal.util.dom.removeClassFromElement(drawer, MASK_CLASS);
		apiInternal.util.dom.removeClassFromElement(document.body, MASK_CLASS);
		apiInternal.slider.releaseDrawerLock(drawerLock);
	}

	/* ========================================================== */

	// Build the content for the tooltip for the Intro step
	function buildIntroTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = apiInternal.util.dom.createAnchorElement('Follow Your Link', apiInternal.lastSubmission.lastSuccessful.link, "_blank");
		var secondaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		secondaryBtn.className = BUTTON_CLASS + " " + BUTTON_CLASS_SECONDARY;
		secondaryBtn.type = 'button';
		secondaryBtn.appendChild(document.createTextNode('Not Now'));

		btns.appendChild(primaryBtn);
		btns.appendChild(secondaryBtn);

		var p1 = document.createElement('p');
		var p2 = document.createElement('p');
		p1.appendChild(document.createTextNode('You just created a new link!'));
		p2.appendChild(document.createTextNode('Following it will take you right back to the highlighted text.'));

		div.appendChild(p1);
		div.appendChild(p2);
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(secondaryBtn, "click", runActionElements);
		apiInternal.addListener(secondaryBtn, "touch", runActionElements);

		apiInternal.addListener(primaryBtn, "click", runActionElements);
		apiInternal.addListener(primaryBtn, "touch", runActionElements);
		apiInternal.addListener(primaryBtn, "click", addFTECookie);
		apiInternal.addListener(primaryBtn, "touch", addFTECookie);

		return div;
	}

	// Build the content for the tooltip for the sharing/saving
	// links
	function buildActionTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Continue'));

		btns.appendChild(primaryBtn);

		div.appendChild(document.createTextNode('Use these buttons to share or save your link.'));
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", runReopenDrawer);
		apiInternal.addListener(primaryBtn, "touch", runReopenDrawer);

		return div;
	}

	// Build the content for the tooltip for reopening the drawer
	function buildReopenTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Done'));

		btns.appendChild(primaryBtn);

		div.appendChild(document.createTextNode('When the sidebar closes, you can reopen it by clicking on your selection.'));
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", closeFTE);
		apiInternal.addListener(primaryBtn, "touch", closeFTE);

		return div;
	}

	// Build the content for the tooltip for viewing the result
	// of a submission
	function buildViewingLinkTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Close Page'));

		btns.appendChild(primaryBtn);

		var p1 = document.createElement('p');
		var p2 = document.createElement('p');
		p1.appendChild(document.createTextNode('Visitors to your new link will see this text highlighted.'));
		p2.appendChild(document.createTextNode('This example opened in a new tab. To go back to your submission, close this page.'));
		p2.style.fontSize = "smaller";
		p2.style.fontStyle = "italic";

		div.appendChild(p1);
		div.appendChild(p2);
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", closeFTE);
		apiInternal.addListener(primaryBtn, "touch", closeFTE);
		apiInternal.addListener(primaryBtn, "click", window.close, window);
		apiInternal.addListener(primaryBtn, "touch", window.close, window);

		return div;
	}

	// Apply a class to the provided list of elements that will make them
	// 'pop out' of the background and display on top of the FTE mask.
	function applyMaskOverrides(elements, opt_background) {
		if(!elements.length) { elements = [elements]; }

		for(var i = 0, len = elements.length; i < len; i++) {
			apiInternal.util.dom.applyClassToElement(elements[i], MASK_OVERRIDE_CLASS);
			elements[i].style.setProperty("z-index", "2147483647", "important");
			if(opt_background) {
				elements[i].style.setProperty("background-color", opt_background, "important");
			}
		}
	}

	// Remove all instances of the MASK_OVERRIDE_CLASS
	function removeMaskOverrides() {
		var overrides = apiInternal.util.dom.getElementsByClassName(MASK_OVERRIDE_CLASS);
		for(var i = overrides.length-1; i >= 0; i--) {
			var override = overrides[i];
			apiInternal.util.dom.removeClassFromElement(override, MASK_OVERRIDE_CLASS);
			override.style.zIndex = '';
			override.style.backgroundColor = '';
		}
	}

	function addFTECookie() {
		var minutesUntilExpire = 1;
		var d = new Date()
		var expires = (d.getTime() + (minutesUntilExpire*60*1000))/1000;

		apiInternal.storage.setCookie('betterlink-fromFTE', 1, expires);
	}

	function removeFTECookie() {
		apiInternal.storage.removeCookie('betterlink-fromFTE');
	}

	function initializeStyles() {
		if(!stylesInitialized) {
			stylesInitialized = true;
			apiInternal.util.dom.createAndAppendStyleElement(CSS);
		}
	}
});
<commit_msg>use variable for cookie name<commit_after>/**
 * Controls the First Time Experience for the user interface.
 *
 */
betterlink_user_interface.createModule("FTE", function(api, apiInternal) {
	api.requireModules( ["Util.DOM", "Storage", "FTE Tooltip", "LastSubmission", "Drawer Slider", "Action Drawer", "Util.HTTP", "HTTP", "Smooth Scrolling", "Event Messaging"] );

	// Element Selectors. These should be dynamic to make the FTE
	// resilient.
	var TOOLTIP_SELECTOR = "#betterlink-tooltip";
	var DRAWER_ID = "betterlink-drawer";
	var NEXUS_CLASS = "betterlink-nexus";
	var PEN_CLASS = "betterlink-pen";
	var SELECTED_CLASS = "betterlink-selected";
	var RESULT_CLASS = api["config"]["highlightCustomCssClass"] || api["config"]["highlightCssClass"];
	var DRAWER_BACKGROUND = "#E9E9E9";

	var FROM_FTE_COOKIE = 'betterlink-fromFTE';

	var BUTTON_CLASS = "betterlink-tooltip-btn";
	var BUTTON_CLASS_SECONDARY = "betterlink-tooltip-btn-secondary";
	var BUTTONS_CLASS = "betterlink-tooltip-btns";
	var MASK_CLASS = "betterlink-fte-mask";
	var MASK_OVERRIDE_CLASS = "betterlink-fte-mask-override";

	var CSS =
		[   TOOLTIP_SELECTOR + " ." + BUTTON_CLASS + " { background-color: #3299BB; border: 1px solid #277799; color: #FFF; font-size: 14px; margin: 5px; padding: 6px 12px; border-radius: .4em; display: inline-block; text-align: center; white-space: nowrap; vertical-align: middle; touch-action: manipulation; cursor: pointer; text-decoration: none; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS + ":hover { background-color: #277799; text-decoration: none; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS_SECONDARY + " { background-color: #BCBCBC; border: 1px solid #9B9B9B; }",
			TOOLTIP_SELECTOR + " ." + BUTTON_CLASS_SECONDARY + ":hover { background-color: #9B9B9B; }",
			TOOLTIP_SELECTOR + " ." + BUTTONS_CLASS + " { text-align: right; width: auto; margin-top: 5px }",
			TOOLTIP_SELECTOR + " p { margin-bottom: 6px; }",

			// The mask has a z-index one less than the maximum
			// Any overrides have a z-index that's the maximum value
			"." + MASK_CLASS + ":after { content: ''; background-color: #000; opacity: 0.6; display: block; position: fixed; top: 0; left: 0; height: 100%; width: 100%; z-index: 2147483646; }",
			"." + MASK_OVERRIDE_CLASS + "{ position: relative !important; box-shadow: 0 0 5px 2px #BCBCBC; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25); }"

		].join(' ');

	var stylesInitialized = false;
	var fteRun = false;
	var drawerLock;

	/****************************************************************************************************/

	apiInternal.events.registerObserverForRemoveBetterlink(closeFTE);
	apiInternal.addInitListener(loadFTE);

	// Register the FTE to run with the next successful submission
	function loadFTE() {
		if(api['config']['enableFTE']) {
			// Queries the server to determine if the user has already
			// viewed the FTE, based on registerFTE()
			apiInternal.checkFTE(decideToLoadFTE);

			// Everytime there is a new submission, refresh that the user
			// doesn't need the FTE. In the event the user *has not* seen
			// it, this will get called along with runIntro the first time.
			apiInternal.lastSubmission.subscribeSuccess.onsuccess(apiInternal.registerFTE);

			// If the user is opening the page and the 'fromFTE' cookie is
			// set, assume the user has just clicked 'Follow Your Link'
			// from the FTE. Once Betterlink highlights the saved content,
			// display the continuation of the FTE.
			if(apiInternal.storage.getCookie(FROM_FTE_COOKIE)) {
				window.setTimeout(runViewingLink, 1000);
			}
		}
	}

	// Asynchronous check for whether we should load the FTE for this
	// user
	function decideToLoadFTE(errorInfo, response) {
		// If we can't get a response from the server, assume the user
		// *has not* seen the FTE. Rationale: we're currently only
		// displaying the FTE on the Betterlink landing page.
		if(errorInfo) {
			apiInternal.lastSubmission.subscribeSuccess.onsuccess(runIntro);
		}
		else {
			// Asynchronously parse the response JSON. Expected result:
			// { fte: '1' } indicates the user has seen the FTE
			apiInternal.util.http.parseJSONServerResponseAsync(response, function(parsedResponse) {
				if(Object.hasOwnProperty.call(parsedResponse, 'fte')) {
					if(parsedResponse['fte'] !== '1') {
						apiInternal.lastSubmission.subscribeSuccess.onsuccess(runIntro);
					}
				}
			});
		}
	}

	// STEP 1
	// This explains that a new link was created and allows the user
	// to demo visiting their new link
	function runIntro() {
		if(!fteRun) {
			// The core interface gets loaded via an init listener, just
			// like loadFTE(). This means we can't guarantee that the
			// drawer won't load *after* we attempt to subscribe the
			// onSuccess event listener. Therefore, we have to run the
			// initialization check here.
			if(!apiInternal.drawer.initialized) {
				fteRun = true;
				return;
			}

			fteRun = true;
			initializeStyles();

			var nexus = apiInternal.util.dom.getElementsByClassName(NEXUS_CLASS)[0];
			var tooltipContent = buildIntroTooltip();

			var drawer = document.getElementById(DRAWER_ID);
			drawerLock = apiInternal.slider.lockDrawerOpen();
			apiInternal.util.dom.applyClassToElement(drawer, MASK_CLASS);

			applyMaskOverrides(nexus, DRAWER_BACKGROUND);
			apiInternal.fteTooltip.addTooltipToDrawer(nexus, tooltipContent);
		}
	}

	// STEP 2
	// This highlights the next steps that are available to the user.
	// Specifically, sharing or saving the link.
	function runActionElements() {
		var pen = apiInternal.util.dom.getElementsByClassName(PEN_CLASS)[0];
		var tooltipContent = buildActionTooltip();

		removeMaskOverrides();
		applyMaskOverrides(pen, DRAWER_BACKGROUND);
		apiInternal.fteTooltip.addTooltipToDrawer(pen, tooltipContent);
	}

	// STEP 3
	// This communicates that the user can click on the previous submission
	// text to re-open the drawer
	function runReopenDrawer() {
		var selectedText = apiInternal.util.dom.getElementsByClassName(SELECTED_CLASS);
		if(selectedText && selectedText.length) {
			var firstSelectedText = selectedText[0];
			var tooltipContent = buildReopenTooltip();

			removeMaskOverrides();
			applyMaskOverrides(selectedText);
			apiInternal.fteTooltip.addTooltipToPage(firstSelectedText, tooltipContent);

			apiInternal.smoothScroll(TOOLTIP_SELECTOR.substr(1), {pixelBuffer: 125});
		}
	}

	// STEP 1b
	// This explains that the user is viewing the result of their submission
	// and directs the user back to the previous page.
	// Note: This step is the first of a newly-loaded page
	function runViewingLink() {
		removeFTECookie();
		var selectedText = apiInternal.util.dom.getElementsByClassName(RESULT_CLASS);
		if(selectedText && selectedText.length) {
			initializeStyles();
			var firstSelectedText = selectedText[0];
			var tooltipContent = buildViewingLinkTooltip();

			// Because the drawer will be closed in this example, we need
			// to apply the overlay to the body
			apiInternal.util.dom.applyClassToElement(document.body, MASK_CLASS);

			applyMaskOverrides(selectedText);
			apiInternal.fteTooltip.addTooltipToPage(firstSelectedText, tooltipContent);

			apiInternal.smoothScroll(TOOLTIP_SELECTOR.substr(1), {pixelBuffer: 75});
		}
	}

	function closeFTE(e) {
		if(e) {
			e.preventDefault ? e.preventDefault() : window.event.returnValue = false;
		}

		var drawer = document.getElementById(DRAWER_ID);
		apiInternal.fteTooltip.remove();
		removeMaskOverrides();
		apiInternal.util.dom.removeClassFromElement(drawer, MASK_CLASS);
		apiInternal.util.dom.removeClassFromElement(document.body, MASK_CLASS);
		apiInternal.slider.releaseDrawerLock(drawerLock);
	}

	/* ========================================================== */

	// Build the content for the tooltip for the Intro step
	function buildIntroTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = apiInternal.util.dom.createAnchorElement('Follow Your Link', apiInternal.lastSubmission.lastSuccessful.link, "_blank");
		var secondaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		secondaryBtn.className = BUTTON_CLASS + " " + BUTTON_CLASS_SECONDARY;
		secondaryBtn.type = 'button';
		secondaryBtn.appendChild(document.createTextNode('Not Now'));

		btns.appendChild(primaryBtn);
		btns.appendChild(secondaryBtn);

		var p1 = document.createElement('p');
		var p2 = document.createElement('p');
		p1.appendChild(document.createTextNode('You just created a new link!'));
		p2.appendChild(document.createTextNode('Following it will take you right back to the highlighted text.'));

		div.appendChild(p1);
		div.appendChild(p2);
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(secondaryBtn, "click", runActionElements);
		apiInternal.addListener(secondaryBtn, "touch", runActionElements);

		apiInternal.addListener(primaryBtn, "click", runActionElements);
		apiInternal.addListener(primaryBtn, "touch", runActionElements);
		apiInternal.addListener(primaryBtn, "click", addFTECookie);
		apiInternal.addListener(primaryBtn, "touch", addFTECookie);

		return div;
	}

	// Build the content for the tooltip for the sharing/saving
	// links
	function buildActionTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Continue'));

		btns.appendChild(primaryBtn);

		div.appendChild(document.createTextNode('Use these buttons to share or save your link.'));
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", runReopenDrawer);
		apiInternal.addListener(primaryBtn, "touch", runReopenDrawer);

		return div;
	}

	// Build the content for the tooltip for reopening the drawer
	function buildReopenTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Done'));

		btns.appendChild(primaryBtn);

		div.appendChild(document.createTextNode('When the sidebar closes, you can reopen it by clicking on your selection.'));
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", closeFTE);
		apiInternal.addListener(primaryBtn, "touch", closeFTE);

		return div;
	}

	// Build the content for the tooltip for viewing the result
	// of a submission
	function buildViewingLinkTooltip() {
		var div = document.createElement('div');
		var btns = document.createElement('div');
		var primaryBtn = document.createElement('button');

		btns.className = BUTTONS_CLASS;
		primaryBtn.className = BUTTON_CLASS;
		primaryBtn.type = 'button';
		primaryBtn.appendChild(document.createTextNode('Close Page'));

		btns.appendChild(primaryBtn);

		var p1 = document.createElement('p');
		var p2 = document.createElement('p');
		p1.appendChild(document.createTextNode('Visitors to your new link will see this text highlighted.'));
		p2.appendChild(document.createTextNode('This example opened in a new tab. To go back to your submission, close this page.'));
		p2.style.fontSize = "smaller";
		p2.style.fontStyle = "italic";

		div.appendChild(p1);
		div.appendChild(p2);
		div.appendChild(btns);

		div.style.width = 'auto';

		apiInternal.addListener(primaryBtn, "click", closeFTE);
		apiInternal.addListener(primaryBtn, "touch", closeFTE);
		apiInternal.addListener(primaryBtn, "click", window.close, window);
		apiInternal.addListener(primaryBtn, "touch", window.close, window);

		return div;
	}

	// Apply a class to the provided list of elements that will make them
	// 'pop out' of the background and display on top of the FTE mask.
	function applyMaskOverrides(elements, opt_background) {
		if(!elements.length) { elements = [elements]; }

		for(var i = 0, len = elements.length; i < len; i++) {
			apiInternal.util.dom.applyClassToElement(elements[i], MASK_OVERRIDE_CLASS);
			elements[i].style.setProperty("z-index", "2147483647", "important");
			if(opt_background) {
				elements[i].style.setProperty("background-color", opt_background, "important");
			}
		}
	}

	// Remove all instances of the MASK_OVERRIDE_CLASS
	function removeMaskOverrides() {
		var overrides = apiInternal.util.dom.getElementsByClassName(MASK_OVERRIDE_CLASS);
		for(var i = overrides.length-1; i >= 0; i--) {
			var override = overrides[i];
			apiInternal.util.dom.removeClassFromElement(override, MASK_OVERRIDE_CLASS);
			override.style.zIndex = '';
			override.style.backgroundColor = '';
		}
	}

	function addFTECookie() {
		var minutesUntilExpire = 1;
		var d = new Date()
		var expires = (d.getTime() + (minutesUntilExpire*60*1000))/1000;

		apiInternal.storage.setCookie(FROM_FTE_COOKIE, 1, expires);
	}

	function removeFTECookie() {
		apiInternal.storage.removeCookie(FROM_FTE_COOKIE);
	}

	function initializeStyles() {
		if(!stylesInitialized) {
			stylesInitialized = true;
			apiInternal.util.dom.createAndAppendStyleElement(CSS);
		}
	}
});
<|endoftext|>