<commit_before>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Radio buttons. See ButtonsDemoView for example usage.
 * This class creates a group of radio buttons in either a horizontal or vertical formation.
 * Each button inherits from RectangularButtonView, and can take a Scenery Node as its content.
 * A typical use case is when you want to have a different modes a of a view to select. Typically,
 * RadioButtonGroup radio buttons display some kind of icon or image, but that is not mandatory.
 *
 * @author Aaron Davis
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var SingleRadioButton = require( 'SUN/buttons/SingleRadioButton' );
  var Color = require( 'SCENERY/util/Color' );
  var RadioButtonGroupAppearance = require( 'SUN/buttons/RadioButtonGroupAppearance' );
  var Property = require( 'AXON/Property' );
  var ColorConstants = require( 'SUN/ColorConstants' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var Shape = require( 'KITE/Shape' );

  /**
   * RadioButtonGroup constructor.
   *
   * @param {Property} property
   * @param {Array} contentArray an array of objects that have two keys each: value and node
   * the node key holds a scenery Node that is the content for a given radio button.
   * the value key should hold the value that the property takes on for the corresponding
   * node to be selected. Optionally, these objects can have an attribute 'label', which is
   * a {Node} used to label the button.
   * @param {Object} [options]
   * @constructor
   */
  function RadioButtonGroup( property, contentArray, options ) {
    assert && assert( !options.hasOwnProperty( 'children' ), 'Cannot pass in children to a RadioButtonGroup, ' +
                                                             'create siblings in the parent node instead' );

    // make sure every object in the content array has properties 'node' and 'value'
    assert && assert( _.every( contentArray, function( obj ) {
      return obj.hasOwnProperty( 'node' ) && obj.hasOwnProperty( 'value' );
    } ), 'contentArray must be an array of objects with properties "node" and "value"' );

    var i; // for loops

    // make sure that each value passed into the contentArray is unique
    var uniqueValues = [];
    for ( i = 0; i < contentArray.length; i++ ) {
      if ( uniqueValues.indexOf( contentArray[i].value ) < 0 ) {
        uniqueValues.push( contentArray[i].value );
      }
      else {
        throw new Error( 'Duplicate value: "' + contentArray[i].value + '" passed into RadioButtonGroup.js' );
      }
    }

    // make sure that the property passed in currently has a value from the contentArray
    if ( uniqueValues.indexOf( property.get() ) === -1 ) {
      throw new Error( 'The property passed in to RadioButtonGroup has an illegal value "' + property.get() +
                       '" that is not present in the contentArray' );
    }

    options = _.extend( {

      //The distance between the radio buttons (as in VBox or HBox)
      spacing: 10,
      orientation: 'vertical',
      enabledProperty: new Property( true ), // whether or not the set of radio buttons as a whole is enabled

      //The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      //Opacity can be set separately for the buttons and button content. If the button content is an Image, setting
      //the opacity on the button as a whole seems to have no effect on it.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      //The following options speciy highlight behavior overrides, leave as null to get the default behavior
      //Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      //These margins are *within* each button
      buttonContentXMargin: 5,
      buttonContentYMargin: 5,

      //TouchArea expansion
      xTouchExpansion: 5,
      yTouchExpansion: 5,

      //MouseArea expansion
      xMouseExpansion: 0,
      yMouseExpansion: 0,

      //The radius for each button
      cornerRadius: 4,

      //How far from the button the text label is (only applies if labels are passed in)
      labelSpacing: 0,

      //Which side of the button the label will appear, options are 'top', 'bottom', 'left', 'right'
      //(only applies if labels are passed in)
      labelAlign: 'bottom',

      //The default appearances use the color values specified above, but other appearances could be specified for more
      //customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: RadioButtonGroupAppearance.defaultRadioButtonsAppearance,
      contentAppearanceStrategy: RadioButtonGroupAppearance.contentAppearanceStrategy
    }, options );

    // calculate the maximum width and height of the content so we can make all radio buttons the same size
    var maxWidth = _.max( contentArray, function( content ) { return content.node.width; } ).node.width;
    var maxHeight = _.max( contentArray, function( content ) { return content.node.height; } ).node.height;

    // make sure all radio buttons are the same size and create the RadioButtons
    var buttons = [];
    var button;
    for ( i = 0; i < contentArray.length; i++ ) {
      options.xMargin = ( ( maxWidth - contentArray[i].node.width ) / 2 ) + options.buttonContentXMargin;
      options.yMargin = ( ( maxHeight - contentArray[i].node.height ) / 2 ) + options.buttonContentYMargin;

      var radioButton = new SingleRadioButton( contentArray[i].value, property, _.extend( { content: contentArray[i].node }, options ) );

      // if a label is given, the button becomes a LayoutBox with the label and button
      if ( contentArray[i].label ) {
        var label = contentArray[i].label;
        var labelOrientation = ( options.labelAlign === 'bottom' || options.labelAlign === 'top' ) ? 'vertical' : 'horizontal';
        var labelChildren = ( options.labelAlign === 'left' || options.labelAlign === 'top' ) ? [label, radioButton] : [radioButton, label];
        button = new LayoutBox( { children: labelChildren, spacing: options.labelSpacing, orientation: labelOrientation } );

        var lineWidth = Math.max( options.selectedLineWidth, options.deselectedLineWidth );
        var xExpand = options.xTouchExpansion;
        var yExpand = options.yTouchExpansion;

        // override the touch and mouse areas defined in RectangularButtonView
        // extra width is added to the SingleRadioButtons so they don't change size if the line width changes,
        // that is why lineWidth is subtracted from the width and height when calculating these new areas
        radioButton.touchArea = Shape.rectangle( -xExpand, -yExpand, button.width + 2 * xExpand - lineWidth, button.height + 2 * yExpand - lineWidth);

        xExpand = options.xMouseExpansion;
        yExpand = options.yMouseExpansion;
        radioButton.mouseArea = Shape.rectangle( -xExpand, -yExpand, button.width + 2 * xExpand - lineWidth, button.height + 2 * yExpand - lineWidth);

        // make sure the label mouse and touch areas don't block the expanded button touch and mouse areas
        label.pickable = false;

        // use the same content appearance strategy for the labels that is used for the button content
        options.contentAppearanceStrategy( label, radioButton.interactionStateProperty, options );
      }
      else {
        button = radioButton;
      }
      buttons.push( button );
    }

    this.enabledProperty = options.enabledProperty;

    // When the entire RadioButtonGroup gets disabled, gray them out and make them unpickable (and vice versa)
    this.enabledProperty.link( function( isEnabled ) {
      for ( i = 0; i < contentArray.length; i++ ) {
        buttons[i].pickable = isEnabled;

        if ( buttons[i] instanceof LayoutBox ) {
          for ( var j = 0; j < 2; j++ ) {
            buttons[i].children[j].enabled = isEnabled;
          }
        }
        else {
          buttons[i].enabled = isEnabled;
        }
      }
    } );

    // make the unselected buttons pickable and have a pointer cursor
    var thisNode = this;
    property.link( function( value ) {
      if ( thisNode.enabledProperty.get() ) {
        for ( i = 0; i < contentArray.length; i++ ) {
          if ( contentArray[i].value === value ) {
            buttons[i].pickable = false;
            buttons[i].cursor = null;
          }
          else {
            buttons[i].pickable = true;
            buttons[i].cursor = 'pointer';
          }
        }
      }
    } );
    options.children = buttons;
    LayoutBox.call( this, options );
  }

  return inherit( LayoutBox, RadioButtonGroup,
    {

      set enabled( value ) {
        assert && assert( typeof value === 'boolean', 'RadioButtonGroup.enabled must be a boolean value' );
        this.enabledProperty.set( value );
      },

      get enabled() { return this.enabledProperty.get(); }

    } );
} );
<commit_msg>use pickable on whole RBG, see #134<commit_after>// Copyright 2002-2014, University of Colorado Boulder

/**
 * Radio buttons. See ButtonsDemoView for example usage.
 * This class creates a group of radio buttons in either a horizontal or vertical formation.
 * Each button inherits from RectangularButtonView, and can take a Scenery Node as its content.
 * A typical use case is when you want to have a different modes a of a view to select. Typically,
 * RadioButtonGroup radio buttons display some kind of icon or image, but that is not mandatory.
 *
 * @author Aaron Davis
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var SingleRadioButton = require( 'SUN/buttons/SingleRadioButton' );
  var Color = require( 'SCENERY/util/Color' );
  var RadioButtonGroupAppearance = require( 'SUN/buttons/RadioButtonGroupAppearance' );
  var Property = require( 'AXON/Property' );
  var ColorConstants = require( 'SUN/ColorConstants' );
  var LayoutBox = require( 'SCENERY/nodes/LayoutBox' );
  var Shape = require( 'KITE/Shape' );

  /**
   * RadioButtonGroup constructor.
   *
   * @param {Property} property
   * @param {Array} contentArray an array of objects that have two keys each: value and node
   * the node key holds a scenery Node that is the content for a given radio button.
   * the value key should hold the value that the property takes on for the corresponding
   * node to be selected. Optionally, these objects can have an attribute 'label', which is
   * a {Node} used to label the button.
   * @param {Object} [options]
   * @constructor
   */
  function RadioButtonGroup( property, contentArray, options ) {
    assert && assert( !options.hasOwnProperty( 'children' ), 'Cannot pass in children to a RadioButtonGroup, ' +
                                                             'create siblings in the parent node instead' );

    // make sure every object in the content array has properties 'node' and 'value'
    assert && assert( _.every( contentArray, function( obj ) {
      return obj.hasOwnProperty( 'node' ) && obj.hasOwnProperty( 'value' );
    } ), 'contentArray must be an array of objects with properties "node" and "value"' );

    var i; // for loops

    // make sure that each value passed into the contentArray is unique
    var uniqueValues = [];
    for ( i = 0; i < contentArray.length; i++ ) {
      if ( uniqueValues.indexOf( contentArray[i].value ) < 0 ) {
        uniqueValues.push( contentArray[i].value );
      }
      else {
        throw new Error( 'Duplicate value: "' + contentArray[i].value + '" passed into RadioButtonGroup.js' );
      }
    }

    // make sure that the property passed in currently has a value from the contentArray
    if ( uniqueValues.indexOf( property.get() ) === -1 ) {
      throw new Error( 'The property passed in to RadioButtonGroup has an illegal value "' + property.get() +
                       '" that is not present in the contentArray' );
    }

    options = _.extend( {

      //The distance between the radio buttons (as in VBox or HBox)
      spacing: 10,
      orientation: 'vertical',
      enabledProperty: new Property( true ), // whether or not the set of radio buttons as a whole is enabled

      //The fill for the rectangle behind the radio buttons.  Default color is bluish color, as in the other button library.
      baseColor: ColorConstants.LIGHT_BLUE,
      disabledBaseColor: ColorConstants.LIGHT_GRAY,

      //Opacity can be set separately for the buttons and button content. If the button content is an Image, setting
      //the opacity on the button as a whole seems to have no effect on it.
      selectedButtonOpacity: 1,
      deselectedButtonOpacity: 0.6,
      selectedContentOpacity: 1,
      deselectedContentOpacity: 0.6,
      overButtonOpacity: 0.8,
      overContentOpacity: 0.8,

      selectedStroke: 'black',
      deselectedStroke: new Color( 50, 50, 50 ),
      selectedLineWidth: 1.5,
      deselectedLineWidth: 1,

      //The following options speciy highlight behavior overrides, leave as null to get the default behavior
      //Note that highlighting applies only to deselected buttons
      overFill: null,
      overStroke: null,
      overLineWidth: null,

      //These margins are *within* each button
      buttonContentXMargin: 5,
      buttonContentYMargin: 5,

      //TouchArea expansion
      xTouchExpansion: 5,
      yTouchExpansion: 5,

      //MouseArea expansion
      xMouseExpansion: 0,
      yMouseExpansion: 0,

      //The radius for each button
      cornerRadius: 4,

      //How far from the button the text label is (only applies if labels are passed in)
      labelSpacing: 0,

      //Which side of the button the label will appear, options are 'top', 'bottom', 'left', 'right'
      //(only applies if labels are passed in)
      labelAlign: 'bottom',

      //The default appearances use the color values specified above, but other appearances could be specified for more
      //customized behavior.  Generally setting the color values above should be enough to specify the desired look.
      buttonAppearanceStrategy: RadioButtonGroupAppearance.defaultRadioButtonsAppearance,
      contentAppearanceStrategy: RadioButtonGroupAppearance.contentAppearanceStrategy
    }, options );

    // calculate the maximum width and height of the content so we can make all radio buttons the same size
    var maxWidth = _.max( contentArray, function( content ) { return content.node.width; } ).node.width;
    var maxHeight = _.max( contentArray, function( content ) { return content.node.height; } ).node.height;

    // make sure all radio buttons are the same size and create the RadioButtons
    var buttons = [];
    var button;
    for ( i = 0; i < contentArray.length; i++ ) {
      options.xMargin = ( ( maxWidth - contentArray[i].node.width ) / 2 ) + options.buttonContentXMargin;
      options.yMargin = ( ( maxHeight - contentArray[i].node.height ) / 2 ) + options.buttonContentYMargin;

      var radioButton = new SingleRadioButton( contentArray[i].value, property, _.extend( { content: contentArray[i].node }, options ) );

      // if a label is given, the button becomes a LayoutBox with the label and button
      if ( contentArray[i].label ) {
        var label = contentArray[i].label;
        var labelOrientation = ( options.labelAlign === 'bottom' || options.labelAlign === 'top' ) ? 'vertical' : 'horizontal';
        var labelChildren = ( options.labelAlign === 'left' || options.labelAlign === 'top' ) ? [label, radioButton] : [radioButton, label];
        button = new LayoutBox( { children: labelChildren, spacing: options.labelSpacing, orientation: labelOrientation } );

        var lineWidth = Math.max( options.selectedLineWidth, options.deselectedLineWidth );
        var xExpand = options.xTouchExpansion;
        var yExpand = options.yTouchExpansion;

        // override the touch and mouse areas defined in RectangularButtonView
        // extra width is added to the SingleRadioButtons so they don't change size if the line width changes,
        // that is why lineWidth is subtracted from the width and height when calculating these new areas
        radioButton.touchArea = Shape.rectangle( -xExpand, -yExpand, button.width + 2 * xExpand - lineWidth, button.height + 2 * yExpand - lineWidth);

        xExpand = options.xMouseExpansion;
        yExpand = options.yMouseExpansion;
        radioButton.mouseArea = Shape.rectangle( -xExpand, -yExpand, button.width + 2 * xExpand - lineWidth, button.height + 2 * yExpand - lineWidth);

        // make sure the label mouse and touch areas don't block the expanded button touch and mouse areas
        label.pickable = false;

        // use the same content appearance strategy for the labels that is used for the button content
        options.contentAppearanceStrategy( label, radioButton.interactionStateProperty, options );
      }
      else {
        button = radioButton;
      }
      buttons.push( button );
    }

    // @private
    this.enabledProperty = options.enabledProperty;

    // super call
    options.children = buttons;
    LayoutBox.call( this, options );
    var thisNode = this;

    // When the entire RadioButtonGroup gets disabled, gray them out and make them unpickable (and vice versa)
    this.enabledProperty.link( function( isEnabled ) {
      thisNode.pickable = isEnabled;

      for ( i = 0; i < contentArray.length; i++ ) {
        if ( buttons[i] instanceof LayoutBox ) {
          for ( var j = 0; j < 2; j++ ) {
            buttons[i].children[j].enabled = isEnabled;
          }
        }
        else {
          buttons[i].enabled = isEnabled;
        }
      }
    } );

    // make the unselected buttons pickable and have a pointer cursor
    property.link( function( value ) {
      if ( thisNode.enabledProperty.get() ) {
        for ( i = 0; i < contentArray.length; i++ ) {
          if ( contentArray[i].value === value ) {
            buttons[i].pickable = false;
            buttons[i].cursor = null;
          }
          else {
            buttons[i].pickable = true;
            buttons[i].cursor = 'pointer';
          }
        }
      }
    } );
  }

  return inherit( LayoutBox, RadioButtonGroup,
    {

      set enabled( value ) {
        assert && assert( typeof value === 'boolean', 'RadioButtonGroup.enabled must be a boolean value' );
        this.enabledProperty.set( value );
      },

      get enabled() { return this.enabledProperty.get(); }

    } );
} );
<|endoftext|>


<commit_before>import _ from 'underscore';
import { Collection } from 'backbone';
import app from '../app';
import Mod from '../models/VerifiedMod';

export default class extends Collection {
  constructor(...args) {
    super(...args);
    this._data = {};
  }

  model(attrs, options) {
    return new Mod(attrs, options);
  }

  modelId(attrs) {
    return attrs.peerID;
  }

  url() {
    return app.localSettings.get('verifiedModsProvider');
  }

  get data() {
    return this._data;
  }

  get localBadge() {
    return {
      large: '../imgs/verifiedModeratorBadge-large.png',
      medium: '../imgs/verifiedModeratorBadge-medium.png',
      small: '../imgs/verifiedModeratorBadge-small.png',
      tiny: '../imgs/verifiedModeratorBadge-tiny.png',
    };
  }

  /**
   * Return a list of verified moderators that match the ids passed in
   * @param IDs {array} - a list of IDs
   */
  matched(IDs) {
    return this.filter(mod => IDs.includes(mod.get('peerID')));
  }

  /**
   * Return a badge object to use to represent the verified moderators available on a listing.
   * If none of the moderators have a badge value, return the local badge files.
   * @param IDs {array} - a list of IDs
   */
  defaultBadge(IDs) {
    const modelWithBadge = _.find(this.matched(IDs), mod => mod.get('type').badge);
    return {
      ...this.localBadge,
      ...(modelWithBadge && modelWithBadge.get('type').badge || {}),
    };
  }

  parse(response) {
    /* The data is expected to be delivered in the following format. There must be at least one
       type with a badge, or the grey loading badge will be shown instead.
    {
      data: {
        name: 'name of provider (required)',
        description: 'description of the provider (optional)',
        link: 'url to the provider (required)'
      },
      types: [
        {
          name: 'standard (required)',
          description: 'description of this type of moderator (optional)',
          badge: {
            large: "url",
            medium: "url",
            small: "url",
            tiny: "url"
          }
        }
      ],
      moderators: [
        {
          peerID: 'QmVFNEj1rv2d3ZqSwhQZW2KT4zsext4cAMsTZRt5dAQqFJ',
          type: 'standard'
        }
      ]
    }
     */
    this._data = response.data || {};
    this._data.url = this.url(); // backup for templates if the link is missing
    const parsedMods = response.moderators ? response.moderators : [];
    /*
       Embed the type in each moderator so it's easier to use elsewhere. It should look like:
       peerID: string,
       type: {
         name: string,
         description: string,
         badge: url string,
         }
     */
    const parsedResponse = [];
    parsedMods.forEach((mod) => {
      if (response.types && response.types.length && mod.type) {
        mod.type = _.findWhere(response.types, { name: mod.type });
        if (mod.type) parsedResponse.push(mod);
      }
    });
    return parsedResponse;
  }
}
<commit_msg>Account for no moderators<commit_after>import _ from 'underscore';
import { Collection } from 'backbone';
import app from '../app';
import Mod from '../models/VerifiedMod';

export default class extends Collection {
  constructor(...args) {
    super(...args);
    this._data = {};
  }

  model(attrs, options) {
    return new Mod(attrs, options);
  }

  modelId(attrs) {
    return attrs.peerID;
  }

  url() {
    return app.localSettings.get('verifiedModsProvider');
  }

  get data() {
    return this._data;
  }

  get localBadge() {
    return {
      large: '../imgs/verifiedModeratorBadge-large.png',
      medium: '../imgs/verifiedModeratorBadge-medium.png',
      small: '../imgs/verifiedModeratorBadge-small.png',
      tiny: '../imgs/verifiedModeratorBadge-tiny.png',
    };
  }

  /**
   * Return a list of verified moderators that match the ids passed in
   * @param IDs {array} - a list of IDs
   */
  matched(IDs = []) {
    return this.filter(mod => IDs.includes(mod.get('peerID')));
  }

  /**
   * Return a badge object to use to represent the verified moderators available on a listing.
   * If none of the moderators have a badge value, return the local badge files.
   * @param IDs {array} - a list of IDs
   */
  defaultBadge(IDs) {
    const modelWithBadge = _.find(this.matched(IDs), mod => mod.get('type').badge);
    return {
      ...this.localBadge,
      ...(modelWithBadge && modelWithBadge.get('type').badge || {}),
    };
  }

  parse(response) {
    /* The data is expected to be delivered in the following format. There must be at least one
       type with a badge, or the grey loading badge will be shown instead.
    {
      data: {
        name: 'name of provider (required)',
        description: 'description of the provider (optional)',
        link: 'url to the provider (required)'
      },
      types: [
        {
          name: 'standard (required)',
          description: 'description of this type of moderator (optional)',
          badge: {
            large: "url",
            medium: "url",
            small: "url",
            tiny: "url"
          }
        }
      ],
      moderators: [
        {
          peerID: 'QmVFNEj1rv2d3ZqSwhQZW2KT4zsext4cAMsTZRt5dAQqFJ',
          type: 'standard'
        }
      ]
    }
     */
    this._data = response.data || {};
    this._data.url = this.url(); // backup for templates if the link is missing
    const parsedMods = response.moderators ? response.moderators : [];
    /*
       Embed the type in each moderator so it's easier to use elsewhere. It should look like:
       peerID: string,
       type: {
         name: string,
         description: string,
         badge: url string,
         }
     */
    const parsedResponse = [];
    parsedMods.forEach((mod) => {
      if (response.types && response.types.length && mod.type) {
        mod.type = _.findWhere(response.types, { name: mod.type });
        if (mod.type) parsedResponse.push(mod);
      }
    });
    return parsedResponse;
  }
}
<|endoftext|>


<commit_before>"use strict";

/*global module, require, setTimeout*/

var d3 = require("d3"),
    textControlsFactory = require("zenpen-toolbar"),
    cssPrefix = require("../css-browser-prefixes.js"),
    simplifiedHTML = require("./dom-to-simplified-html.js")(),
    diffOperations = require("./diff-operations.js"),

    enterKey = 13;

/*
 Floating edit elements for one node.

 The contents, position and size will be altered to fit the node currently being edited.
 */
module.exports = function(body, getNodeCollection, viewport, transitions, update) {
    var zoom = viewport.zoom,
	nodeId = null,
	width = width,
	height = height,
	x,
	y,

	maybeFocus = function() {
	    if (document.activeElement === name.node() ||
		document.activeElement === description.node() ||
		document.activeElement === editor.node()
	       ) {
		   return;
	       } else {
		   name.node().focus();
	       }
	},

	disableEditor = function() {
	    viewport.uncentreNode();
	    update();
	},

	editor = body.append("div")
	    .classed("text-editor", true),

	name = editor.append("div")
	    .classed("edit-name", true)
	    .attr("contenteditable", true)
	    .on("keypress", function() {
		if (d3.event.which === enterKey) {
		    /*
		     Prevent newlines.
		     The user can still paste newlines, but these will get removed on save.
		     */
		    d3.event.stopPropagation();
		    d3.event.preventDefault();
		}
	    })
	    .on("input", function() {
		var node = getNodeCollection().get(nodeId);

		node.modifyName(
		    diffOperations(
			node.name(),
			name.text()
		    )
		);
	    }),

	textControls = textControlsFactory(body),

	description = editor.append("div")
	    .classed("edit-description", true)
	    .attr("contenteditable", true),

	positionEditor = function(translate, scale) {
	    /*
	     Our editor gets the wrong position equal to half its size multiplied by (scale - 1).

	     Adding half its size in the first translate doesn't appear to correct this.

	     I believe the problem may be due to the origin of transformation being in a slightly different place for svg and css transforms, but was not able to resolve it nicely.
	     */
	    var enlargement = scale - 1,
		hackedSize = [
		    translate[0] + (enlargement * width / 2),
		    translate[1] + (enlargement * height / 2)
		];

	    cssPrefix(
	    	editor,
	    	"transform",
		// Pan
		"translate(" + hackedSize[0] + "px," + hackedSize[1] + "px)" +
		    " " +
		    // Zoom
	    	    "scale(" + scale + ")" +
	    	    " " +
		    // Offset for node position + margins
	    	    "translate(" + x + "px," + y + "px)"
	    );
	};

    textControls(
	description,
	function() {
	    var node = getNodeCollection().get(nodeId);
	    node.modifyDescription(
		diffOperations(
		    node.description(),
		    simplifiedHTML(description.node())
		)
	    );
	}
    );

    viewport.zoom.onZoom(positionEditor);

    return {
	update: function(svgNodes) {
	    var changedNode = nodeId === viewport.getCentredNodeId();

	    nodeId = viewport.getCentredNodeId();

	    var node = getNodeCollection().get(nodeId);

	    if (!node) {
		editor.style("visibility", "hidden");
		textControls.update();
		return;
	    }

	    var	svgNode = svgNodes.filter(function(d, i) {
		return d.id === nodeId;
	    });

	    if (svgNode.size() !== 1) {
		/*
		 We didn't get asked to redraw the important node.
		 */
		return;
	    }

	    var nodeViewModel = svgNode.datum();

	    width = nodeViewModel.innerWidth,
	    height = nodeViewModel.innerHeight;
	    x = nodeViewModel.margin.horizontal + nodeViewModel.x;
	    y = nodeViewModel.margin.top + nodeViewModel.y;

	    positionEditor(zoom.translate(), zoom.scale());

	    editor
		.style("visibility", "visible");

	    transitions.maybeTransition(editor)
		.style("width", width + "px")
		.style("height", height + "px");

	    if (name.text() !== nodeViewModel.name) {
		name.text(
		    nodeViewModel.name
		);
	    }

	    if (description.html() !== nodeViewModel.description) {
		description.html(
		    nodeViewModel.description
		);
	    }

	    maybeFocus();
	    textControls.update();
	}
    };
};
<commit_msg>IE compatibility for name editing.<commit_after>"use strict";

/*global module, require, setTimeout*/

var d3 = require("d3"),
    textControlsFactory = require("zenpen-toolbar"),
    cssPrefix = require("../css-browser-prefixes.js"),
    simplifiedHTML = require("./dom-to-simplified-html.js")(),
    diffOperations = require("./diff-operations.js"),

    enterKey = 13;

/*
 Floating edit elements for one node.

 The contents, position and size will be altered to fit the node currently being edited.
 */
module.exports = function(body, getNodeCollection, viewport, transitions, update) {
    var zoom = viewport.zoom,
	nodeId = null,
	width = width,
	height = height,
	x,
	y,

	maybeFocus = function() {
	    if (document.activeElement === name.node() ||
		document.activeElement === description.node() ||
		document.activeElement === editor.node()
	       ) {
		   return;
	       } else {
		   name.node().focus();
	       }
	},

	disableEditor = function() {
	    viewport.uncentreNode();
	    update();
	},

	editor = body.append("div")
	    .classed("text-editor", true),

	saveName = function() {
	    var node = getNodeCollection().get(nodeId);

	    if (node) {
		node.modifyName(
		    diffOperations(
			node.name(),
			name.text()
		    )
		);
	    }
	},

	name = editor.append("div")
	    .classed("edit-name", true)
	    .attr("contenteditable", true)
	    .on("keypress", function() {
		if (d3.event.which === enterKey) {
		    /*
		     Prevent newlines.
		     The user can still paste newlines, but these will get removed on save.
		     */
		    d3.event.stopPropagation();
		    d3.event.preventDefault();
		}
	    })
    // "input" is the correct event here - others included for Internet Explorer compatibility.
	    .on("keyup", saveName)
    	    .on("paste", saveName)
	    .on("blur", saveName)
	    .on("input", saveName),

	textControls = textControlsFactory(body),

	description = editor.append("div")
	    .classed("edit-description", true)
	    .attr("contenteditable", true),

	positionEditor = function(translate, scale) {
	    /*
	     Our editor gets the wrong position equal to half its size multiplied by (scale - 1).

	     Adding half its size in the first translate doesn't appear to correct this.

	     I believe the problem may be due to the origin of transformation being in a slightly different place for svg and css transforms, but was not able to resolve it nicely.
	     */
	    var enlargement = scale - 1,
		hackedSize = [
		    translate[0] + (enlargement * width / 2),
		    translate[1] + (enlargement * height / 2)
		];

	    cssPrefix(
	    	editor,
	    	"transform",
		// Pan
		"translate(" + hackedSize[0] + "px," + hackedSize[1] + "px)" +
		    " " +
		    // Zoom
	    	    "scale(" + scale + ")" +
	    	    " " +
		    // Offset for node position + margins
	    	    "translate(" + x + "px," + y + "px)"
	    );
	};

    textControls(
	description,
	function() {
	    var node = getNodeCollection().get(nodeId);
	    node.modifyDescription(
		diffOperations(
		    node.description(),
		    simplifiedHTML(description.node())
		)
	    );
	}
    );

    viewport.zoom.onZoom(positionEditor);

    return {
	update: function(svgNodes) {
	    var changedNode = nodeId === viewport.getCentredNodeId();

	    nodeId = viewport.getCentredNodeId();

	    var node = getNodeCollection().get(nodeId);

	    if (!node) {
		editor.style("visibility", "hidden");
		textControls.update();
		return;
	    }

	    var	svgNode = svgNodes.filter(function(d, i) {
		return d.id === nodeId;
	    });

	    if (svgNode.size() !== 1) {
		/*
		 We didn't get asked to redraw the important node.
		 */
		return;
	    }

	    var nodeViewModel = svgNode.datum();

	    width = nodeViewModel.innerWidth,
	    height = nodeViewModel.innerHeight;
	    x = nodeViewModel.margin.horizontal + nodeViewModel.x;
	    y = nodeViewModel.margin.top + nodeViewModel.y;

	    positionEditor(zoom.translate(), zoom.scale());

	    editor
		.style("visibility", "visible");

	    transitions.maybeTransition(editor)
		.style("width", width + "px")
		.style("height", height + "px");

	    if (name.text() !== nodeViewModel.name) {
		name.text(
		    nodeViewModel.name
		);
	    }

	    if (description.html() !== nodeViewModel.description) {
		description.html(
		    nodeViewModel.description
		);
	    }

	    maybeFocus();
	    textControls.update();
	}
    };
};
<|endoftext|>


<commit_before>'use strict';

!function($) {

  let Motion = Foundation.Motion; // import { Motion } from "foundation.util.motion";

const MutationObserver = (function () {
  var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
  for (var i=0; i < prefixes.length; i++) {
    if (`${prefixes[i]}MutationObserver` in window) {
      return window[`${prefixes[i]}MutationObserver`];
    }
  }
  return false;
}());

const triggers = (el, type) => {
  el.data(type).split(' ').forEach(id => {
    $(`#${id}`)[ type === 'close' ? 'trigger' : 'triggerHandler'](`${type}.zf.trigger`, [el]);
  });
};
// Elements with [data-open] will reveal a plugin that supports it when clicked.
$(document).on('click.zf.trigger', '[data-open]', function() {
  triggers($(this), 'open');
});

// Elements with [data-close] will close a plugin that supports it when clicked.
// If used without a value on [data-close], the event will bubble, allowing it to close a parent component.
$(document).on('click.zf.trigger', '[data-close]', function() {
  let id = $(this).data('close');
  if (id) {
    triggers($(this), 'close');
  }
  else {
    $(this).trigger('close.zf.trigger');
  }
});

// Elements with [data-toggle] will toggle a plugin that supports it when clicked.
$(document).on('click.zf.trigger', '[data-toggle]', function() {
  let id = $(this).data('toggle');
  if (id) {
    triggers($(this), 'toggle');
  } else {
    $(this).trigger('toggle.zf.trigger');
  }
});

// Elements with [data-closable] will respond to close.zf.trigger events.
$(document).on('close.zf.trigger', '[data-closable]', function(e){
  e.stopPropagation();
  let animation = $(this).data('closable');

  if(animation !== ''){
    Motion.animateOut($(this), animation, function() {
      $(this).trigger('closed.zf');
    });
  }else{
    $(this).fadeOut().trigger('closed.zf');
  }
});

$(document).on('focus.zf.trigger blur.zf.trigger', '[data-toggle-focus]', function() {
  let id = $(this).data('toggle-focus');
  $(`#${id}`).triggerHandler('toggle.zf.trigger', [$(this)]);
});

/**
* Fires once after all other scripts have loaded
* @function
* @private
*/
$(window).on('load', () => {
  checkListeners();
});

function checkListeners() {
  eventsListener();
  resizeListener();
  scrollListener();
  closemeListener();
}

//******** only fires this function once on load, if there's something to watch ********
function closemeListener(pluginName) {
  var yetiBoxes = $('[data-yeti-box]'),
      plugNames = ['dropdown', 'tooltip', 'reveal'];

  if(pluginName){
    if(typeof pluginName === 'string'){
      plugNames.push(pluginName);
    }else if(typeof pluginName === 'object' && typeof pluginName[0] === 'string'){
      plugNames.concat(pluginName);
    }else{
      console.error('Plugin names must be strings');
    }
  }
  if(yetiBoxes.length){
    let listeners = plugNames.map((name) => {
      return `closeme.zf.${name}`;
    }).join(' ');

    $(window).off(listeners).on(listeners, function(e, pluginId){
      let plugin = e.namespace.split('.')[0];
      let plugins = $(`[data-${plugin}]`).not(`[data-yeti-box="${pluginId}"]`);

      plugins.each(function(){
        let _this = $(this);

        _this.triggerHandler('close.zf.trigger', [_this]);
      });
    });
  }
}

function resizeListener(debounce){
  let timer,
      $nodes = $('[data-resize]');
  if($nodes.length){
    $(window).off('resize.zf.trigger')
    .on('resize.zf.trigger', function(e) {
      if (timer) { clearTimeout(timer); }

      timer = setTimeout(function(){

        if(!MutationObserver){//fallback for IE 9
          $nodes.each(function(){
            $(this).triggerHandler('resizeme.zf.trigger');
          });
        }
        //trigger all listening elements and signal a resize event
        $nodes.attr('data-events', "resize");
      }, debounce || 10);//default time to emit resize event
    });
  }
}

function scrollListener(debounce){
  let timer,
      $nodes = $('[data-scroll]');
  if($nodes.length){
    $(window).off('scroll.zf.trigger')
    .on('scroll.zf.trigger', function(e){
      if(timer){ clearTimeout(timer); }

      timer = setTimeout(function(){

        if(!MutationObserver){//fallback for IE 9
          $nodes.each(function(){
            $(this).triggerHandler('scrollme.zf.trigger');
          });
        }
        //trigger all listening elements and signal a scroll event
        $nodes.attr('data-events', "scroll");
      }, debounce || 10);//default time to emit scroll event
    });
  }
}

function eventsListener() {
  if(!MutationObserver){ return false; }
  let nodes = document.querySelectorAll('[data-resize], [data-scroll], [data-mutate]');

  //element callback
  var listeningElementsMutation = function (mutationRecordsList) {
      var $target = $(mutationRecordsList[0].target);

	  //trigger the event handler for the element depending on type
      switch (mutationRecordsList[0].type) {

        case "attributes":
          if ($target.attr("data-events") === "scroll" && mutationRecordsList[0].attributeName === "data-events") {
		  	$target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]);
		  }
		  if ($target.attr("data-events") === "resize" && mutationRecordsList[0].attributeName === "data-events") {
		  	$target.triggerHandler('resizeme.zf.trigger', [$target]);
		   }
		  if (mutationRecordsList[0].attributeName === "style") {
			  $target.closest("[data-mutate]").attr("data-events","mutate");
			  $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
		  }
		  break;

        case "childList":
		  $target.closest("[data-mutate]").attr("data-events","mutate");
		  $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
          break;

        default:
          return false;
        //nothing
      }
    };

    if (nodes.length) {
      //for each element that needs to listen for resizing, scrolling, or mutation add a single observer
      for (var i = 0; i <= nodes.length - 1; i++) {
        var elementObserver = new MutationObserver(listeningElementsMutation);
        elementObserver.observe(nodes[i], { attributes: true, childList: true, characterData: false, subtree: true, attributeFilter: ["data-events", "style"] });
      }
    }
  }

// ------------------------------------

// [PH]
// Foundation.CheckWatchers = checkWatchers;
Foundation.IHearYou = checkListeners;
// Foundation.ISeeYou = scrollListener;
// Foundation.IFeelYou = closemeListener;

}(jQuery);
<commit_msg>Begin refactoring Triggers, move open listener into new structure<commit_after>'use strict';

!function($) {

  let Motion = Foundation.Motion; // import { Motion } from "foundation.util.motion";

const MutationObserver = (function () {
  var prefixes = ['WebKit', 'Moz', 'O', 'Ms', ''];
  for (var i=0; i < prefixes.length; i++) {
    if (`${prefixes[i]}MutationObserver` in window) {
      return window[`${prefixes[i]}MutationObserver`];
    }
  }
  return false;
}());

const triggers = (el, type) => {
  el.data(type).split(' ').forEach(id => {
    $(`#${id}`)[ type === 'close' ? 'trigger' : 'triggerHandler'](`${type}.zf.trigger`, [el]);
  });
};

var Triggers = {
  jQueryListeners: {
    openListener: function() {
      triggers($(this), 'open');
    }
  }
};

// Elements with [data-open] will reveal a plugin that supports it when clicked.
Triggers.addOpenListener = ($elem) => {
  $elem.off('click.zf.trigger', Triggers.jQueryListeners.openListener);
  $elem.on('click.zf.trigger', '[data-open]', Triggers.jQueryListeners.openListener);
}

// Elements with [data-close] will close a plugin that supports it when clicked.
// If used without a value on [data-close], the event will bubble, allowing it to close a parent component.
$(document).on('click.zf.trigger', '[data-close]', function() {
  let id = $(this).data('close');
  if (id) {
    triggers($(this), 'close');
  }
  else {
    $(this).trigger('close.zf.trigger');
  }
});

// Elements with [data-toggle] will toggle a plugin that supports it when clicked.
$(document).on('click.zf.trigger', '[data-toggle]', function() {
  let id = $(this).data('toggle');
  if (id) {
    triggers($(this), 'toggle');
  } else {
    $(this).trigger('toggle.zf.trigger');
  }
});

// Elements with [data-closable] will respond to close.zf.trigger events.
$(document).on('close.zf.trigger', '[data-closable]', function(e){
  e.stopPropagation();
  let animation = $(this).data('closable');

  if(animation !== ''){
    Motion.animateOut($(this), animation, function() {
      $(this).trigger('closed.zf');
    });
  }else{
    $(this).fadeOut().trigger('closed.zf');
  }
});

$(document).on('focus.zf.trigger blur.zf.trigger', '[data-toggle-focus]', function() {
  let id = $(this).data('toggle-focus');
  $(`#${id}`).triggerHandler('toggle.zf.trigger', [$(this)]);
});

/**
* Fires once after all other scripts have loaded
* @function
* @private
*/
$(window).on('load', () => {
  checkListeners();
});

function checkListeners() {
  eventsListener();
  resizeListener();
  scrollListener();
  closemeListener();
}

//******** only fires this function once on load, if there's something to watch ********
function closemeListener(pluginName) {
  var yetiBoxes = $('[data-yeti-box]'),
      plugNames = ['dropdown', 'tooltip', 'reveal'];

  if(pluginName){
    if(typeof pluginName === 'string'){
      plugNames.push(pluginName);
    }else if(typeof pluginName === 'object' && typeof pluginName[0] === 'string'){
      plugNames.concat(pluginName);
    }else{
      console.error('Plugin names must be strings');
    }
  }
  if(yetiBoxes.length){
    let listeners = plugNames.map((name) => {
      return `closeme.zf.${name}`;
    }).join(' ');

    $(window).off(listeners).on(listeners, function(e, pluginId){
      let plugin = e.namespace.split('.')[0];
      let plugins = $(`[data-${plugin}]`).not(`[data-yeti-box="${pluginId}"]`);

      plugins.each(function(){
        let _this = $(this);

        _this.triggerHandler('close.zf.trigger', [_this]);
      });
    });
  }
}

function resizeListener(debounce){
  let timer,
      $nodes = $('[data-resize]');
  if($nodes.length){
    $(window).off('resize.zf.trigger')
    .on('resize.zf.trigger', function(e) {
      if (timer) { clearTimeout(timer); }

      timer = setTimeout(function(){

        if(!MutationObserver){//fallback for IE 9
          $nodes.each(function(){
            $(this).triggerHandler('resizeme.zf.trigger');
          });
        }
        //trigger all listening elements and signal a resize event
        $nodes.attr('data-events', "resize");
      }, debounce || 10);//default time to emit resize event
    });
  }
}

function scrollListener(debounce){
  let timer,
      $nodes = $('[data-scroll]');
  if($nodes.length){
    $(window).off('scroll.zf.trigger')
    .on('scroll.zf.trigger', function(e){
      if(timer){ clearTimeout(timer); }

      timer = setTimeout(function(){

        if(!MutationObserver){//fallback for IE 9
          $nodes.each(function(){
            $(this).triggerHandler('scrollme.zf.trigger');
          });
        }
        //trigger all listening elements and signal a scroll event
        $nodes.attr('data-events', "scroll");
      }, debounce || 10);//default time to emit scroll event
    });
  }
}

function eventsListener() {
  if(!MutationObserver){ return false; }
  let nodes = document.querySelectorAll('[data-resize], [data-scroll], [data-mutate]');

  //element callback
  var listeningElementsMutation = function (mutationRecordsList) {
      var $target = $(mutationRecordsList[0].target);

	  //trigger the event handler for the element depending on type
      switch (mutationRecordsList[0].type) {

        case "attributes":
          if ($target.attr("data-events") === "scroll" && mutationRecordsList[0].attributeName === "data-events") {
		  	$target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]);
		  }
		  if ($target.attr("data-events") === "resize" && mutationRecordsList[0].attributeName === "data-events") {
		  	$target.triggerHandler('resizeme.zf.trigger', [$target]);
		   }
		  if (mutationRecordsList[0].attributeName === "style") {
			  $target.closest("[data-mutate]").attr("data-events","mutate");
			  $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
		  }
		  break;

        case "childList":
		  $target.closest("[data-mutate]").attr("data-events","mutate");
		  $target.closest("[data-mutate]").triggerHandler('mutateme.zf.trigger', [$target.closest("[data-mutate]")]);
          break;

        default:
          return false;
        //nothing
      }
    };

    if (nodes.length) {
      //for each element that needs to listen for resizing, scrolling, or mutation add a single observer
      for (var i = 0; i <= nodes.length - 1; i++) {
        var elementObserver = new MutationObserver(listeningElementsMutation);
        elementObserver.observe(nodes[i], { attributes: true, childList: true, characterData: false, subtree: true, attributeFilter: ["data-events", "style"] });
      }
    }
  }

  Triggers.init = function($) {
    let $document = $(document);
    Triggers.addOpenListener($document);
  }

  Triggers.init($);

// ------------------------------------

// [PH]
// Foundation.CheckWatchers = checkWatchers;
Foundation.IHearYou = checkListeners;
// Foundation.ISeeYou = scrollListener;
// Foundation.IFeelYou = closemeListener;

}(jQuery);
<|endoftext|>


<commit_before>/*   RSence
 *   Copyright 2006 Riassence Inc.
 *   http://riassence.com/
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this software package. If not, contact licensing@riassence.com
 */

/*** = Description
  ** Main container of global operations on +HView+ and
  ** +HApplication+ -derived classes.
  **
  ** HSystem is used to keep +HApplication+ and +HView+ instances
  ** globally managed. The managed classes themself calls +HSystem+ methods,
  ** so there is no real need to access +HSystem+ directly from user-level code.
  **
***/
var//RSence.Foundation
HSystem = {

/** When the focus behaviour is 1, clicking on any subview brings
  * the window to front, if attached to a HWindow instance.
  * If the behaviour is 0, only direct clicks on the HWindow controls
  * brings the window to front.
  *
  **/
  windowFocusMode: 1,

/** Singleton class; has no constructor **/
  // constructor: null,

/** An array of HApplication instances, index is the appId **/
  apps: [],

/** An array (in the same order as apps): holds priority values **/
  appPriorities: [],

/** An array (in the same order as apps): holds busy status **/
  busyApps: [],

/** This array holds free app id:s **/
  freeAppIds: [],

/** The default HSystem ticker interval. Unit is milliseconds. **/
  defaultInterval: 10,

/** The default HApplication priority. Unit is "On the n:th tick: poll". **/
  defaultPriority: 20,

/** The z-index of root-level +HView+ instances. All the array operations
  * are done by the inner logic of +HApplication+ and +HView+ instances.
  **/
  viewsZOrder: [],

/** This is the internal "clock" counter. Gets updated on every tick. **/
  ticks: 0,

/** Time in milliseconds for the timeout of a poll to finish before
  * being classified as stuck and thus forcibly terminated.
  **/
  maxAppRunTime: 5000,

/** Calls applications, uses the prority as a prioritizer.
  **/
  scheduler: function(){

    // Loop through all applications:
    for( var _appId=0; _appId<this.apps.length; _appId++ ){
      // Check, if the application exists:
      if( this.apps[ _appId ] ){
        // Check, if the application is busy:
        if( !this.busyApps[ _appId ] ){
          // Check, if the tick count matches the priority of the app:
          if( (this.ticks % this.appPriorities[ _appId ]) === 0 ){
            // Set the app busy, the app itself should "unbusy" itself, when the idle call is done.
            // That happens in <HApplication._startIdle>

            // If the app is not busy, then make a idle call:
            if(HSystem.apps[_appId]){
              HSystem.apps[_appId]._startIdle();
            }
          }
        }
      }
    }

    if(this._updateZIndexOfChildrenBuffer.length!==0){
      this._flushUpdateZIndexOfChilden();
    }

  },

  _updateFlexibleRects: function(){
    var
    _view,
    i = 0;
    for( ; i < this.views.length; i++ ){
      _view = this.views[i];
      if(_view && (_view.flexRight || _view.flexBottom)){
        _view.rect._updateFlexibleDimensions();
      }
    }
  },

/** Calls the scheduler and then calls itself after a timeout to keep
  * the loop going on.
  **/
  ticker: function(){
    // Increment the tick counter:
    this.ticks++;
    this.scheduler();
    this._tickTimeout = setTimeout( function(){HSystem.ticker();},this.defaultInterval);
  },

/** = Description
  * Adds the structures needed for a new +HApplication+ instance.
  *
  * Called from inside the +HApplication+ constructor.
  * Binds an app and gives it a unique id.
  *
  * = Parameters
  * +_app+::       The reference to the +HApplication+ instance object.
  * +_priority+::  The app priority.
  *
  * = Returns
  * The app id.
  *
  **/
  addApp: function(_app, _priority){
    var _appId;
    if(this.freeAppIds.length !== 0){
      _appId = this.freeAppIds.shift();
      this.apps[_appId] = _app;
    }
    else {
      this.apps.push(_app);
      _appId = this.apps.length-1;
    }

    // sets self as parent
    _app.parent  = this;
    _app.parents = [this];

    _app.appId = _appId;

    this.startApp(_appId, _priority);

    return _appId;
  },

/** = Description
  * Starts polling an app instance (and its components).
  *
  * = Parameters
  * +_appId+::      The unique id of the app.
  * +_priority+::   The app priority.
  *
  **/
  startApp: function(_appId,_priority){
    if(_priority===undefined){
      _priority = this.defaultInterval;
    }
    this.appPriorities[ _appId ] = _priority;
    this.busyApps[_appId] = false;
  },

/** = Description
  * Stops polling an app instance (and its components).
  *
  * = Parameters
  * +_appId+::   The id of the app.
  *
  **/
  stopApp: function(_appId){
    this.busyApps[_appId] = true;
  },

/** = Description
  * Changes the priority of the app. Calls +stopApp+ and +startApp+.
  *
  * = Parameters
  * +_appId+::     The id of the app.
  * +_priority+::  The app priority.
  *
  **/
  reniceApp: function(_appId,_priority){
    this.appPriorities[ _appId ] = _priority;
  },

/** = Description
  * Stops polling and deletes an app instance (and its components).
  *
  * = Parameters
  * +_appId+::    The unique id of the app.
  * +_forced+::   (Optional) The doesn't wait for the last poll to finish.
  *
  **/
  killApp: function(_appId, _forced){
    if( !_forced ){
      var _startedWaiting = new Date().getTime();
      while( this.busyApps[ _appId ] === true ) {
        /* Waiting for the app to finish its idle loop... */
        if (new Date().getTime() > _startedWaiting + this.maxAppRunTime) {
          break;
        }
      }
    }
    this.busyApps[_appId] = true;

    this.apps[ _appId ].destroyAllViews();
    this.apps[ _appId ] = null;

    this.freeAppIds.push( _appId );
  },

/** All +HView+ instances that are defined **/
  views: [],

/** List of free +viwes+ indexes **/
  _freeViewIds: [],

/** = Description
  * Adds a view and assigns it an id.
  *
  * = Parameters
  * +_view+::   The +HView+ instance.
  *
  * = Returns
  * The new view id.
  *
  **/
  addView: function(_view){
    var _newId;
    if(this._freeViewIds.length===0){
      _newId = this.views.length;
      this.views.push(_view);
    }
    else {
      _newId = this._freeViewIds.pop();
      this.views[_newId] = _view;
    }
    return _newId;
  },

/** = Description
  * Removes a view and recycles its id.
  *
  * = Parameters
  * +_viewId+::  The view id to delete.
  *
  **/
  delView: function(_viewId){
    this.views[_viewId] = null;
    this._freeViewIds.push(_viewId);
  },

/** The view id of the active window. 0 means none. **/
  activeWindowId: 0,

/** = Description
  * Focuses the window given and blurs the previous one.
  *
  * = Parameters
  * +_view+::   The +HView+ instance, this is almost always a
  *             +HWindow+ instance.
  *
  **/
  windowFocus: function(_view){
    if(!_view){
      this.activeWindowId=0;
      return;
    }
    var _activeWindowId = this.activeWindowId,
        _views = this.views,
        _viewId = _view.viewId;
    if(_views[_activeWindowId]){
      if (_views[_activeWindowId]["windowBlur"]) {
        _views[_activeWindowId].windowBlur();
      }
    }
    this.activeWindowId=_viewId;
    console.log('windowFocus, zindex:', ELEM.getStyle( _view.elemId, 'z-index', true ) );
    _view.bringToFront();
    _view.windowFocus();
  },

/** optimization of zindex buffer, see +HView+ **/
  _updateZIndexOfChildrenBuffer: [],

/** Updates the z-indexes of the children of the given +_viewId+. **/
  updateZIndexOfChildren: function(_viewId) {
    if(this._updateZIndexOfChildrenBuffer.indexOf(_viewId)===-1){
      this._updateZIndexOfChildrenBuffer.push(_viewId);
    }
    if((_viewId !== undefined && _viewId !== null) && (this.views[_viewId].app === this.views[_viewId].parent)){
      (this._updateZIndexOfChildrenBuffer.indexOf(null)===-1) && this._updateZIndexOfChildrenBuffer.push(null);
    }
  },

/** Flushes the z-indexes. This is a fairly expensive operation,
  * thas's why the info is buffered.
  **/
  _flushUpdateZIndexOfChilden: function() {

    var

    j = 0, // buffer index

    // reference to the HSystem namespace
    _this = HSystem,

    // reference to the buffer
    _buffer = _this._updateZIndexOfChildrenBuffer,

    // the length of the buffer
    _bufLen = _buffer.length;

    // loop buffer length times to get the items
    for ( ; j < _bufLen; j++ ) {

      // get and remove view the view id from the z-index flush status buffer:
      var
      _viewId = _buffer.shift(),

      // reference to the view's z-index array or the system root-level views if _viewId is null
      _views = ((_viewId === null)?(_this.viewsZOrder):(_this.views[ _viewId ].viewsZOrder)),

      // the length of the view's z-index array
      _viewLen = _views.length,

      // reference to the setStyle method of the element manager
      _setStyl = ELEM.setStyle,

      // reference to HSystem.views (collection of all views, by index)
      _sysViews = _this.views,

      // assign variables for use inside the inner loop:

      // the viewId of the view to be updated
      _subViewId,

      // the view itself with the viewId above
      _view,

      // the elemId property, used as a [] -lookup in the loop
      _elemIdStr = 'elemId',

      // the css property name
      _zIdxStr = 'z-index',

      // the loop index
      i=0,

      // the element id of the view
      _elemId;

      // end of var declarations

      // loop through all subviews and update the indexes:
      for ( ; i < _viewLen; i++ ) {

        // get the viewId to be updated based on the z-index array
        _subViewId = _views[ i ];

        // reference to the view itself
        _view = _sysViews[ _subViewId ];

        // the element id of the view
        _elemId = _view[ _elemIdStr ];

        // do the element manager call itself to update the dom property
        _setStyl( _elemId, _zIdxStr, i );
      }
    }
  }
};

// });

// Starts the ticking, when the document is loaded:
LOAD(
  function(){
    HSystem.ticker();
  }
);
<commit_msg>extra debug log message removed.<commit_after>/*   RSence
 *   Copyright 2006 Riassence Inc.
 *   http://riassence.com/
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this software package. If not, contact licensing@riassence.com
 */

/*** = Description
  ** Main container of global operations on +HView+ and
  ** +HApplication+ -derived classes.
  **
  ** HSystem is used to keep +HApplication+ and +HView+ instances
  ** globally managed. The managed classes themself calls +HSystem+ methods,
  ** so there is no real need to access +HSystem+ directly from user-level code.
  **
***/
var//RSence.Foundation
HSystem = {

/** When the focus behaviour is 1, clicking on any subview brings
  * the window to front, if attached to a HWindow instance.
  * If the behaviour is 0, only direct clicks on the HWindow controls
  * brings the window to front.
  *
  **/
  windowFocusMode: 1,

/** Singleton class; has no constructor **/
  // constructor: null,

/** An array of HApplication instances, index is the appId **/
  apps: [],

/** An array (in the same order as apps): holds priority values **/
  appPriorities: [],

/** An array (in the same order as apps): holds busy status **/
  busyApps: [],

/** This array holds free app id:s **/
  freeAppIds: [],

/** The default HSystem ticker interval. Unit is milliseconds. **/
  defaultInterval: 10,

/** The default HApplication priority. Unit is "On the n:th tick: poll". **/
  defaultPriority: 20,

/** The z-index of root-level +HView+ instances. All the array operations
  * are done by the inner logic of +HApplication+ and +HView+ instances.
  **/
  viewsZOrder: [],

/** This is the internal "clock" counter. Gets updated on every tick. **/
  ticks: 0,

/** Time in milliseconds for the timeout of a poll to finish before
  * being classified as stuck and thus forcibly terminated.
  **/
  maxAppRunTime: 5000,

/** Calls applications, uses the prority as a prioritizer.
  **/
  scheduler: function(){

    // Loop through all applications:
    for( var _appId=0; _appId<this.apps.length; _appId++ ){
      // Check, if the application exists:
      if( this.apps[ _appId ] ){
        // Check, if the application is busy:
        if( !this.busyApps[ _appId ] ){
          // Check, if the tick count matches the priority of the app:
          if( (this.ticks % this.appPriorities[ _appId ]) === 0 ){
            // Set the app busy, the app itself should "unbusy" itself, when the idle call is done.
            // That happens in <HApplication._startIdle>

            // If the app is not busy, then make a idle call:
            if(HSystem.apps[_appId]){
              HSystem.apps[_appId]._startIdle();
            }
          }
        }
      }
    }

    if(this._updateZIndexOfChildrenBuffer.length!==0){
      this._flushUpdateZIndexOfChilden();
    }

  },

  _updateFlexibleRects: function(){
    var
    _view,
    i = 0;
    for( ; i < this.views.length; i++ ){
      _view = this.views[i];
      if(_view && (_view.flexRight || _view.flexBottom)){
        _view.rect._updateFlexibleDimensions();
      }
    }
  },

/** Calls the scheduler and then calls itself after a timeout to keep
  * the loop going on.
  **/
  ticker: function(){
    // Increment the tick counter:
    this.ticks++;
    this.scheduler();
    this._tickTimeout = setTimeout( function(){HSystem.ticker();},this.defaultInterval);
  },

/** = Description
  * Adds the structures needed for a new +HApplication+ instance.
  *
  * Called from inside the +HApplication+ constructor.
  * Binds an app and gives it a unique id.
  *
  * = Parameters
  * +_app+::       The reference to the +HApplication+ instance object.
  * +_priority+::  The app priority.
  *
  * = Returns
  * The app id.
  *
  **/
  addApp: function(_app, _priority){
    var _appId;
    if(this.freeAppIds.length !== 0){
      _appId = this.freeAppIds.shift();
      this.apps[_appId] = _app;
    }
    else {
      this.apps.push(_app);
      _appId = this.apps.length-1;
    }

    // sets self as parent
    _app.parent  = this;
    _app.parents = [this];

    _app.appId = _appId;

    this.startApp(_appId, _priority);

    return _appId;
  },

/** = Description
  * Starts polling an app instance (and its components).
  *
  * = Parameters
  * +_appId+::      The unique id of the app.
  * +_priority+::   The app priority.
  *
  **/
  startApp: function(_appId,_priority){
    if(_priority===undefined){
      _priority = this.defaultInterval;
    }
    this.appPriorities[ _appId ] = _priority;
    this.busyApps[_appId] = false;
  },

/** = Description
  * Stops polling an app instance (and its components).
  *
  * = Parameters
  * +_appId+::   The id of the app.
  *
  **/
  stopApp: function(_appId){
    this.busyApps[_appId] = true;
  },

/** = Description
  * Changes the priority of the app. Calls +stopApp+ and +startApp+.
  *
  * = Parameters
  * +_appId+::     The id of the app.
  * +_priority+::  The app priority.
  *
  **/
  reniceApp: function(_appId,_priority){
    this.appPriorities[ _appId ] = _priority;
  },

/** = Description
  * Stops polling and deletes an app instance (and its components).
  *
  * = Parameters
  * +_appId+::    The unique id of the app.
  * +_forced+::   (Optional) The doesn't wait for the last poll to finish.
  *
  **/
  killApp: function(_appId, _forced){
    if( !_forced ){
      var _startedWaiting = new Date().getTime();
      while( this.busyApps[ _appId ] === true ) {
        /* Waiting for the app to finish its idle loop... */
        if (new Date().getTime() > _startedWaiting + this.maxAppRunTime) {
          break;
        }
      }
    }
    this.busyApps[_appId] = true;

    this.apps[ _appId ].destroyAllViews();
    this.apps[ _appId ] = null;

    this.freeAppIds.push( _appId );
  },

/** All +HView+ instances that are defined **/
  views: [],

/** List of free +viwes+ indexes **/
  _freeViewIds: [],

/** = Description
  * Adds a view and assigns it an id.
  *
  * = Parameters
  * +_view+::   The +HView+ instance.
  *
  * = Returns
  * The new view id.
  *
  **/
  addView: function(_view){
    var _newId;
    if(this._freeViewIds.length===0){
      _newId = this.views.length;
      this.views.push(_view);
    }
    else {
      _newId = this._freeViewIds.pop();
      this.views[_newId] = _view;
    }
    return _newId;
  },

/** = Description
  * Removes a view and recycles its id.
  *
  * = Parameters
  * +_viewId+::  The view id to delete.
  *
  **/
  delView: function(_viewId){
    this.views[_viewId] = null;
    this._freeViewIds.push(_viewId);
  },

/** The view id of the active window. 0 means none. **/
  activeWindowId: 0,

/** = Description
  * Focuses the window given and blurs the previous one.
  *
  * = Parameters
  * +_view+::   The +HView+ instance, this is almost always a
  *             +HWindow+ instance.
  *
  **/
  windowFocus: function(_view){
    if(!_view){
      this.activeWindowId=0;
      return;
    }
    var _activeWindowId = this.activeWindowId,
        _views = this.views,
        _viewId = _view.viewId;
    if(_views[_activeWindowId]){
      if (_views[_activeWindowId]["windowBlur"]) {
        _views[_activeWindowId].windowBlur();
      }
    }
    this.activeWindowId=_viewId;
    _view.bringToFront();
    _view.windowFocus();
  },

/** optimization of zindex buffer, see +HView+ **/
  _updateZIndexOfChildrenBuffer: [],

/** Updates the z-indexes of the children of the given +_viewId+. **/
  updateZIndexOfChildren: function(_viewId) {
    if(this._updateZIndexOfChildrenBuffer.indexOf(_viewId)===-1){
      this._updateZIndexOfChildrenBuffer.push(_viewId);
    }
    if((_viewId !== undefined && _viewId !== null) && (this.views[_viewId].app === this.views[_viewId].parent)){
      (this._updateZIndexOfChildrenBuffer.indexOf(null)===-1) && this._updateZIndexOfChildrenBuffer.push(null);
    }
  },

/** Flushes the z-indexes. This is a fairly expensive operation,
  * thas's why the info is buffered.
  **/
  _flushUpdateZIndexOfChilden: function() {

    var

    j = 0, // buffer index

    // reference to the HSystem namespace
    _this = HSystem,

    // reference to the buffer
    _buffer = _this._updateZIndexOfChildrenBuffer,

    // the length of the buffer
    _bufLen = _buffer.length;

    // loop buffer length times to get the items
    for ( ; j < _bufLen; j++ ) {

      // get and remove view the view id from the z-index flush status buffer:
      var
      _viewId = _buffer.shift(),

      // reference to the view's z-index array or the system root-level views if _viewId is null
      _views = ((_viewId === null)?(_this.viewsZOrder):(_this.views[ _viewId ].viewsZOrder)),

      // the length of the view's z-index array
      _viewLen = _views.length,

      // reference to the setStyle method of the element manager
      _setStyl = ELEM.setStyle,

      // reference to HSystem.views (collection of all views, by index)
      _sysViews = _this.views,

      // assign variables for use inside the inner loop:

      // the viewId of the view to be updated
      _subViewId,

      // the view itself with the viewId above
      _view,

      // the elemId property, used as a [] -lookup in the loop
      _elemIdStr = 'elemId',

      // the css property name
      _zIdxStr = 'z-index',

      // the loop index
      i=0,

      // the element id of the view
      _elemId;

      // end of var declarations

      // loop through all subviews and update the indexes:
      for ( ; i < _viewLen; i++ ) {

        // get the viewId to be updated based on the z-index array
        _subViewId = _views[ i ];

        // reference to the view itself
        _view = _sysViews[ _subViewId ];

        // the element id of the view
        _elemId = _view[ _elemIdStr ];

        // do the element manager call itself to update the dom property
        _setStyl( _elemId, _zIdxStr, i );
      }
    }
  }
};

// });

// Starts the ticking, when the document is loaded:
LOAD(
  function(){
    HSystem.ticker();
  }
);
<|endoftext|>


<commit_before>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Main entry point for the 'Graphing Quadratics' sim.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Sim from '../../joist/js/Sim.js';
import simLauncher from '../../joist/js/simLauncher.js';
import Tandem from '../../tandem/js/Tandem.js';
import ExploreScreen from './explore/ExploreScreen.js';
import FocusAndDirectrixScreen from './focusanddirectrix/FocusAndDirectrixScreen.js';
import graphingQuadraticsStrings from './graphingQuadraticsStrings.js';
import StandardFormScreen from './standardform/StandardFormScreen.js';
import VertexFormScreen from './vertexform/VertexFormScreen.js';

const options = {
  credits: {
    leadDesign: 'Amanda McGarry',
    softwareDevelopment: 'Chris Malley (PixelZoom, Inc.), Andrea Lin',
    team: 'Mike Dubson, Karina K. R. Hensberry, Trish Loeblein, Ariel Paul, Kathy Perkins',
    qualityAssurance: 'Jaspe Arias, Steele Dalton, Laura Rea, Jacob Romero, Ethan Ward, Kathryn Woessner, Kelly Wurtz'
  }
};

simLauncher.launch( () => {
  const screens = [
    new ExploreScreen( Tandem.ROOT.createTandem( 'exploreScreen' ) ),
    new StandardFormScreen( Tandem.ROOT.createTandem( 'standardFormScreen' ) ),
    new VertexFormScreen( Tandem.ROOT.createTandem( 'vertexFormScreen' ) ),
    new FocusAndDirectrixScreen( Tandem.ROOT.createTandem( 'focusAndDirectrixScreen' ) )
  ];
  const sim = new Sim( graphingQuadraticsStrings[ 'graphing-quadratics' ].title, screens, options );
  sim.start();
} );<commit_msg>phetioDesigned:true, https://github.com/phetsims/graphing-quadratics/issues/152<commit_after>// Copyright 2014-2020, University of Colorado Boulder

/**
 * Main entry point for the 'Graphing Quadratics' sim.
 *
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */

import Sim from '../../joist/js/Sim.js';
import simLauncher from '../../joist/js/simLauncher.js';
import Tandem from '../../tandem/js/Tandem.js';
import ExploreScreen from './explore/ExploreScreen.js';
import FocusAndDirectrixScreen from './focusanddirectrix/FocusAndDirectrixScreen.js';
import graphingQuadraticsStrings from './graphingQuadraticsStrings.js';
import StandardFormScreen from './standardform/StandardFormScreen.js';
import VertexFormScreen from './vertexform/VertexFormScreen.js';

simLauncher.launch( () => {

  const screens = [
    new ExploreScreen( Tandem.ROOT.createTandem( 'exploreScreen' ) ),
    new StandardFormScreen( Tandem.ROOT.createTandem( 'standardFormScreen' ) ),
    new VertexFormScreen( Tandem.ROOT.createTandem( 'vertexFormScreen' ) ),
    new FocusAndDirectrixScreen( Tandem.ROOT.createTandem( 'focusAndDirectrixScreen' ) )
  ];

  const options = {
    credits: {
      leadDesign: 'Amanda McGarry',
      softwareDevelopment: 'Chris Malley (PixelZoom, Inc.), Andrea Lin',
      team: 'Mike Dubson, Karina K. R. Hensberry, Trish Loeblein, Ariel Paul, Kathy Perkins',
      qualityAssurance: 'Jaspe Arias, Steele Dalton, Laura Rea, Jacob Romero, Ethan Ward, Kathryn Woessner, Kelly Wurtz'
    },

    // phet-io options
    phetioDesigned: true
  };

  const sim = new Sim( graphingQuadraticsStrings[ 'graphing-quadratics' ].title, screens, options );
  sim.start();
} );<|endoftext|>


<commit_before>// imageUploadButton plugin
// - open a file select dialog when the user clicks the target,
// call handleFiles with a fileList of the selected files.
;(function() {
    jQuery.fn.imageUploadButton = function(handleFiles, options) {
        return this.fileUploadButton(handleFiles, $.extend({
            allowedMimeTypes: ['image/png','image/jpeg','image/gif']
        }, options);
    }
})();<commit_msg>Bug fix<commit_after>// imageUploadButton plugin
// - open a file select dialog when the user clicks the target,
// call handleFiles with a fileList of the selected files.
;(function() {
    jQuery.fn.imageUploadButton = function(handleFiles, options) {
        return this.fileUploadButton(handleFiles, $.extend({
            allowedMimeTypes: ['image/png','image/jpeg','image/gif']
        }, options));
    }
})();<|endoftext|>


<commit_before>/*
Copyright (c) 2012 Sergio Gabriel Teves
All rights reserved.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

(function($, undefined ) {

    $.widget( "mobile.datebox", $.mobile.widget, {
        options: {
            theme: 'jqm',
            preset: 'date'
        },
        _create: function() {
            var o = $.extend(this.options, this.element.data('options')),
                input = $(this.element);
            input.scroller(o);
        }
    });

    $(document).bind("pagebeforecreate", function(c) {
        // Convert date inputs to normal inputs
        $('input[type="date"]:jqmData(role="datebox")', c.target).prop('type', 'text');
    });

    $(document).bind("pagecreate create", function(c) {
        $(document).trigger("dateboxbeforecreate");

        $(':jqmData(role="datebox")', c.target).each(function() {
            if (typeof ($(this).data("datebox")) === "undefined") {
                $(this).datebox();
            }
        })
    });

})( jQuery );

<commit_msg>Rename data-role attribute in the JQM plugin<commit_after>/*
Copyright (c) 2012 Sergio Gabriel Teves
All rights reserved.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

(function($, undefined ) {

    $.widget( "mobile.mobiscroll", $.mobile.widget, {
        options: {
            theme: 'jqm',
            preset: 'date',
            animate: 'pop'
        },
        _create: function() {
            var input = this.element,
                o = $.extend(this.options, input.jqmData('options'));
            input.scroller(o);
        }
    });

    $(document).bind("pagebeforecreate", function(c) {
        // Convert date inputs to normal inputs
        $('input[type="date"]:jqmData(role="mobiscroll")', c.target).prop('type', 'text');
    });

    $(document).bind("pagecreate create", function(c) {
        $(document).trigger("mobiscrollbeforecreate");

        $(':jqmData(role="mobiscroll")', c.target).each(function() {
            if (typeof ($(this).data("mobiscroll")) === "undefined") {
                $(this).mobiscroll();
            }
        })
    });

})( jQuery );

<|endoftext|>


<commit_before>/*
 *  Project: reader-placesaver.js
 *  Description: A jQuery plugin that saves a reader's place (scroll position) when reading online.
 *  Author: Scott A. Murray <design@carbonvictory.com>
 *  License: GNU General Public License (http://www.gnu.org/licenses/gpl-3.0.txt)
 */

;(function($, document, undefined)
{

	function resume_reading(unique_page_key)
	{

		var saved_scroll_position = $.cookie(settings['unique_page_key']);

		if (saved_scroll_position != null) {
			$('html, body').animate({scrollTop: saved_scroll_position}, 'slow');
		}

		return saved_scroll_position;

	}

	function is_visible(window, element_id)
	{

		var cur_window_top 		= window.scrollTop();
		var cur_window_bottom 	= cur_window_top + window.height();

		var element_top		= $(element_id).offset().top;
		var element_bottom	= element_top + $(element_id).height();

		return ( (element_bottom >= cur_window_top) &&
				 (element_top <= cur_window_bottom) &&
				 (element_bottom <= cur_window_bottom) &&
				 (element_top >= cur_window_top) );

	}

	function save_place(window, current_scroll_position, sensitivity, unique_page_key)
	{

		var new_scroll_position = window.scrollTop();

		if (new_scroll_position - current_scroll_position >= sensitivity)
		{
			$.cookie(unique_page_key, new_scroll_position, { expires: 2 });
			return new_scroll_position;
		}

	}

	function end_reading(unique_page_key)
	{
		$.cookie(unique_page_key, null);
	}

	$.fn.placesaver = function(options)
	{

		/*
		 *	$(window).placesaver();
		 *
		 *	unique_page_key: a string identifier ___unique to the page___ (a page id or hash of the canonical url works well)
		 *	sensitivity: how many pixels the user must scroll before a new position is saved
		 *	clear_onfinish: if true, the saved reading position will be cleared when the clear_element is wholly visible
		 *	clear_element: the id of the element that triggers clear_onfinish, usually a footer element after the page's content
		 *
		 *	It's recommended that you always pass a unique_page_key and clear_element (if clear_onfinish is set to true)
		 */
		var settings = $.extend({
			'unique_page_key'	: preg_replace('/[^[:alnum:]]/', '', window.location.pathname),
			'sensitivity'		: 100,
			'clear_onfinish' 	: true,
			'clear_element'		: 'footer'
		}, options);

		// set up a variable to store the current scroll position
		var current_scroll_position = 0;

		return this.each(function(settings)
		{
			current_scroll_position = resume_reading(settings['unique_page_key']);
		});

		this.scroll(function() {

			current_scroll_position = save_place(this, current_scroll_position, settings['sensitivity'], settings['unique_page_key']);
			if (settings['clear_onfinish'] == true && is_visible(settings['clear_element']))
			{
				end_reading(settings['unique_page_key']);
			}

		});

	};

})( jQuery );<commit_msg>let's try it<commit_after>/*
 *  Project: reader-placesaver.js
 *  Description: A jQuery plugin that saves a reader's place (scroll position) when reading online.
 *  Author: Scott A. Murray <design@carbonvictory.com>
 *  License: GNU General Public License (http://www.gnu.org/licenses/gpl-3.0.txt)
 */

;(function($, document, undefined)
{

	function resume_reading(unique_page_key)
	{

		var saved_scroll_position = $.cookie(settings['unique_page_key']);

		if (saved_scroll_position != null) {
			$('html, body').animate({scrollTop: saved_scroll_position}, 'slow');
		}

		return saved_scroll_position;

	}

	function is_visible(window, element_id)
	{

		var cur_window_top 		= window.scrollTop();
		var cur_window_bottom 	= cur_window_top + window.height();

		var element_top		= $(element_id).offset().top;
		var element_bottom	= element_top + $(element_id).height();

		return ( (element_bottom >= cur_window_top) &&
				 (element_top <= cur_window_bottom) &&
				 (element_bottom <= cur_window_bottom) &&
				 (element_top >= cur_window_top) );

	}

	function save_place(window, current_scroll_position, sensitivity, unique_page_key)
	{

		var new_scroll_position = window.scrollTop();

		if (new_scroll_position - current_scroll_position >= sensitivity)
		{
			$.cookie(unique_page_key, new_scroll_position, { expires: 2 });
			return new_scroll_position;
		}

	}

	function end_reading(unique_page_key)
	{
		$.cookie(unique_page_key, null);
	}

	$.fn.placesaver = function(options)
	{

		/*
		 *	$(window).placesaver();
		 *
		 *	unique_page_key: a string identifier ___unique to the page___ (a page id or hash of the canonical url works well)
		 *	sensitivity: how many pixels the user must scroll before a new position is saved
		 *	clear_onfinish: if true, the saved reading position will be cleared when the clear_element is wholly visible
		 *	clear_element: the id of the element that triggers clear_onfinish, usually a footer element after the page's content
		 *
		 *	It's recommended that you always pass a unique_page_key and clear_element (if clear_onfinish is set to true)
		 */
		var settings = $.extend({
			'unique_page_key'	: preg_replace('/[^[:alnum:]]/', '', window.location.pathname),
			'sensitivity'		: 100,
			'clear_onfinish' 	: true,
			'clear_element'		: 'footer'
		}, options);

		// set up a variable to store the current scroll position
		var current_scroll_position = 0;

		return this.each(function()
		{
			current_scroll_position = resume_reading(settings.unique_page_key);
		});

		this.scroll(function() {

			current_scroll_position = save_place(this, current_scroll_position, settings.sensitivity, settings.unique_page_key);
			if (settings.clear_onfinish == true && is_visible(settings.clear_element))
			{
				end_reading(settings.unique_page_key);
			}

		});

	};

})( jQuery );<|endoftext|>