<commit_before>class MomentHelperInit {
  // let self = this;

  //
  constructor ( isClient ) {

    if ( isClient ) {
      // create a new reactiveVar that holds the moment helper context of locale
      this.currentLocale = new ReactiveVar();

      // when creating the moment helper context, set the locale same as moment
      this.currentLocale.set( moment.locale() );

      // reactive now
      this.now = new ReactiveVar( moment() );
      var now = this.now;

      Meteor.setInterval( function () {
        now.set( moment() );
      }, 1000 ); // every second
    }

    // logging messages
    this._msg = {
      dateNotValidReturnNow:
        'valid date not provided, sending new moment instead',
      dateNotValidReturnNull:
        'valid date not provided, sending null'
    };

    // initate the default options
    this.options = {
      // if a helper is called and no date given, create one as now
      returnNowIfDateNotGiven: false,
      // extra console logging
      debug: false,
      // initial library of format tokens
      formatTokens: {
        'default': 'LLL'
        // defaults to locale date format: Month name, day of month, year, time
      }
    };

  }

  // configuration function, merges the defaults with the options provided
  configure ( options ) {
    _.extend( this.options, options );
  }

  // expose a utility to set the locale, updating moment & reactive locale
  setLocale ( locale ) {
    moment.locale( locale );
    this.currentLocale.set( moment.locale() );// how does 'this' work?
  }

  // logging function
  log ( log ) {

    // delete the currently stored log
    // this was enabled to assit with logging
    delete this.logged;

    // if debugging is enabled, log it!
    if ( this.options.debug ) {
      this.logged = log;
      console.log( log );
    }
  }

  // used for moFormat, helps to get a format token eg. 'YYYY-MM-DD'
  _getToken ( token, aMoment ) {
    check( token, Match.Optional( String, null ) );
    let tokenLibrary = _.defaults( this.options.formatTokens, {
      // these tokens will always be available (unless overridden)
      'dayOfWeek': 'dddd',
      'dayOfMonth': 'D',
      'month': 'M',
      'year': 'YYYY',
      'time': 'h:mm a'
    });

    // if no token provided, use the default from the token library
    if ( !token ) {
      token = 'default';
    }

    // see if the token is a reference to the token library, otherwise keep
    token = tokenLibrary[token] || token;

    // check if token is a function & process
    if ( _.isFunction( token ) ) {
      token = token.call( aMoment );
    }

    // return the token
    return token;
  }

  _getMoment ( obj ) {
    check( obj, Match.Optional(
      Match.OneOf(
        Match.Where( moment.isMoment ),
        Date, String, null
        )
      )
    );
    var result;
    var date;

    // goal is to get a moment object from what is provided
    if ( moment.isMoment( obj ) ) {
      // if a moment is provided, return that moment, no questions asked
      result = obj;
    } else if ( _.isDate( obj ) ) {
      // if a date is provided, convert to moment and return
      result = moment( obj );
    } else if ( _.isString( obj ) && obj.length > 1 ) {
      // attempt to get a date from the string

      // if '|' is found, separate and use the RHS as the input format token
      if ( obj.indexOf( '|' ) !== -1 ) {
        date = moment(
          obj.substring( 0, obj.indexOf( '|' ) ), // input string LHS of '|'
          obj.substring( obj.indexOf( '|' ) + 1 ) // input format, RHS after '|'
        );
      } else {
        date = moment( new Date( obj ) );
      }
      if ( date.isValid() ) {
        result = date;
      }
    }

    // could not get a moment object, work out what to return if anything
    if ( !result ) {
      if ( this.options.returnNowIfDateNotGiven ) {
        this.log( this._msg.dateNotValidReturnNow );
        return moment();
      } else {
        this.log( this._msg.dateNotValidReturnNull );
      }
    }

    return result;
  }

}

mo = new MomentHelperInit( Meteor.isClient );
<commit_msg>Remove check<commit_after>class MomentHelperInit {
  // let self = this;

  //
  constructor ( isClient ) {

    if ( isClient ) {
      // create a new reactiveVar that holds the moment helper context of locale
      this.currentLocale = new ReactiveVar();

      // when creating the moment helper context, set the locale same as moment
      this.currentLocale.set( moment.locale() );

      // reactive now
      this.now = new ReactiveVar( moment() );
      var now = this.now;

      Meteor.setInterval( function () {
        now.set( moment() );
      }, 1000 ); // every second
    }

    // logging messages
    this._msg = {
      dateNotValidReturnNow:
        'valid date not provided, sending new moment instead',
      dateNotValidReturnNull:
        'valid date not provided, sending null'
    };

    // initate the default options
    this.options = {
      // if a helper is called and no date given, create one as now
      returnNowIfDateNotGiven: false,
      // extra console logging
      debug: false,
      // initial library of format tokens
      formatTokens: {
        'default': 'LLL'
        // defaults to locale date format: Month name, day of month, year, time
      }
    };

  }

  // configuration function, merges the defaults with the options provided
  configure ( options ) {
    _.extend( this.options, options );
  }

  // expose a utility to set the locale, updating moment & reactive locale
  setLocale ( locale ) {
    moment.locale( locale );
    this.currentLocale.set( moment.locale() );// how does 'this' work?
  }

  // logging function
  log ( log ) {

    // delete the currently stored log
    // this was enabled to assit with logging
    delete this.logged;

    // if debugging is enabled, log it!
    if ( this.options.debug ) {
      this.logged = log;
      console.log( log );
    }
  }

  // used for moFormat, helps to get a format token eg. 'YYYY-MM-DD'
  _getToken ( token, aMoment ) {
    check( token, Match.Optional( String, null ) );
    let tokenLibrary = _.defaults( this.options.formatTokens, {
      // these tokens will always be available (unless overridden)
      'dayOfWeek': 'dddd',
      'dayOfMonth': 'D',
      'month': 'M',
      'year': 'YYYY',
      'time': 'h:mm a'
    });

    // if no token provided, use the default from the token library
    if ( !token ) {
      token = 'default';
    }

    // see if the token is a reference to the token library, otherwise keep
    token = tokenLibrary[token] || token;

    // check if token is a function & process
    if ( _.isFunction( token ) ) {
      token = token.call( aMoment );
    }

    // return the token
    return token;
  }

  _getMoment ( obj ) {

    var result;
    var date;

    // goal is to get a moment object from what is provided
    if ( moment.isMoment( obj ) ) {
      // if a moment is provided, return that moment, no questions asked
      result = obj;
    } else if ( _.isDate( obj ) ) {
      // if a date is provided, convert to moment and return
      result = moment( obj );
    } else if ( _.isString( obj ) && obj.length > 1 ) {
      // attempt to get a date from the string

      // if '|' is found, separate and use the RHS as the input format token
      if ( obj.indexOf( '|' ) !== -1 ) {
        date = moment(
          obj.substring( 0, obj.indexOf( '|' ) ), // input string LHS of '|'
          obj.substring( obj.indexOf( '|' ) + 1 ) // input format, RHS after '|'
        );
      } else {
        date = moment( new Date( obj ) );
      }
      if ( date.isValid() ) {
        result = date;
      }
    }

    // could not get a moment object, work out what to return if anything
    if ( !result ) {
      if ( this.options.returnNowIfDateNotGiven ) {
        this.log( this._msg.dateNotValidReturnNow );
        return moment();
      } else {
        this.log( this._msg.dateNotValidReturnNull );
      }
    }

    return result;
  }

}

mo = new MomentHelperInit( Meteor.isClient );
<|endoftext|>


<commit_before>(function(){
    'use strict';

    /**
     * @ngdoc directive
     * @name upBoardApp.directive:ubDropIn
     * @description
     * # dropIn
     */
    angular.module('upBoardApp')
      .directive('ubDropIn', dropIn);

    dropIn.$inject = ['$log', '$timeout', '$document', 'utility'];
    function dropIn($log, $timeout, $document, utility) {
        return {
          templateUrl: '/app/frontend/scripts/directives/ub-drop-in.tpl.html',
          restrict: 'E',
          replace: true,
          scope:{
              data: "="
          },
          link: function(scope, element, attrs){

              var savedData; // use to remember for exit transition
              scope.messages = [];

              // Matter module aliases
              var Engine = Matter.Engine,
                  World = Matter.World,
                  Body = Matter.Body,
                  Bodies = Matter.Bodies,
                  Common = Matter.Common,
                  Composite = Matter.Composite,
                  Composites = Matter.Composites,
                  Events = Matter.Events,
                  MouseConstraint = Matter.MouseConstraint;

              // extend base Render class, modify how body ids are displayed
              var CustomRender = {};
              angular.extend(CustomRender, Matter.Render);

              CustomRender.bodyIds = function(engine, bodies, context) {
                  var c = context,
                      i,
                      j;

                  for (i = 0; i < bodies.length; i++) {
                      if (!bodies[i].render.visible)
                          continue;

                      var parts = bodies[i].parts;
                      for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
                          var part = parts[j];
                          c.font = '14px Helvetica';
                          c.textAlign = 'center';
                          c.fillStyle = 'rgba(255,255,255,1)';
                          c.fillText(part.id - 5, part.position.x, part.position.y);
                      }
                  }
              };

              // create a Matter.js engine, use CustomRender
              var engine = Engine.create(document.getElementById('dropInPoint'), {
                render: {
                  controller: CustomRender,
                  options: {
                    showAngleIndicator: false,
                    wireframes: false,
                    background: '#ffffff',
                    showIds: true
              //            width: 1000,
              //            height: 1000
                  }
                }
              });

              // add a mouse controlled constraint
              var mouseConstraint = MouseConstraint.create(engine);
              World.add(engine.world, mouseConstraint);

              var offset = 0,
                  options = {
                      isStatic: true,
                      render: {
                          visible: false
                      }
                  };

              engine.world.bodies = [];

              // these static walls will not be rendered in this sprites example, see options
              World.add(engine.world, [
                  Bodies.rectangle(400, -offset, 800.5 + 2 * offset, 5, options),
                  Bodies.rectangle(400, 600 + offset, 800.5 + 2 * offset, 5, options),
                  Bodies.rectangle(800 + offset, 300, 5, 600.5 + 2 * offset, options),
                  Bodies.rectangle(-offset, 300, 5, 600.5 + 2 * offset, options)
              ]);

              //                      var stack = Composites.stack(20, 20, 10, 10, 0, 0, function(x, y) {
              //                          if (Common.random() > 0.35) {
              //                              return Bodies.rectangle(x, y, 64, 64, {
              //                //                  render: {
              //                //                      strokeStyle: '#ffffff',
              //                //                      sprite: {
              //                //                          texture: 'http://brm.io/matter-js-demo-master/img/box.png'
              //                //                      }
              //                //                  }
              //                              });
              //                          } else {
              //                              return Bodies.circle(x, y, Common.random(30,50), {
              //                                  density: 0.0005,
              //                                  frictionAir: 0.06,
              //                                  restitution: 0.3,
              //                                  friction: 0.01,
              //                //                  render: {
              //                //                      sprite: {
              //                //                          texture: 'http://brm.io/matter-js-demo-master/img/ball.png'
              //                //                      }
              //                //                  }
              //                              });
              //                          }
              //                      });

              //                      var comp = Composite.create();
              //
              //                //      var stack = Composites.
              //                      for(var i = 0; i < 20; i++){
              //                        comp.bodies.push(Bodies.circle(10, 20, Common.random(30,40), {
              //                            density: 0.0005,
              //                            frictionAir: 0.06,
              //                            restitution: 0.3,
              //                            friction: 0.01
              //                        }));
              //                      }
              //World.add(engine.world, stack);

              //      var rect = Bodies.rectangle(20, 20, 64, 64);
              //
              //
              //      World.add(engine.world, rect);
              //      }
              scope.engine = engine;
              scope.world = World;
              scope.bodies = Bodies;

              //                    Events.on(engine, 'afterRender', function(event) {
              //                        var context = engine.render.context;
              //                        context.font = "45px 'Cabin Sketch'";
              //                        context.fillText("THROW OBJECT HERE", 150, 80);
              //                    });

              //      var renderOptions = engine.render.options;
              //      renderOptions.background = 'http://brm.io/matter-js-demo-master/img/wall-bg.jpg';
              //      renderOptions.background = '#ffffff';
              //      renderOptions.showAngleIndicator = false;
              //      renderOptions.wireframes = false;

              // run the engine
              Engine.run(engine);

              scope.$watch(attrs.ngShow, function(){

                var isShown = scope.$eval(attrs.ngShow);

                if(isShown){

                  //

                  $log.debug('about to show ' + scope.data.type);
                  utility.setEntryTransition(element, scope.data);
                  savedData = scope.data;
                }
                else if(savedData != null){
                  $log.debug('about to hide ' + savedData.type + ', next type on deck: ' + scope.data.type);
                  utility.setExitTransition(element, savedData);
                  savedData = null;
                }
              });

            $document.on("click", function(event){

              var x = 400, y = 0;
              var body;

              // TODO pick better colors
              var colorArr = ['#CF4858','#F6624A','#1B6A81','#16A79D','#80628B','#DC557A','#F4AC42','#4F8598','#69B1CB'];

              var fillColor = Common.choose(colorArr);

              // TODO add another random polygon
              if (Common.random() > 0.35) {
                var randDim = Common.random(50, 70);
                body = Bodies.rectangle(x, y, randDim, randDim,{
                  render:{
                    fillStyle: fillColor,
                    strokeStyle: '#ffffff'
                  }
                });
              }
              else {
                body = Bodies.circle(x, y, Common.random(30,50), {
                    density: 0.0005,
                    frictionAir: 0.06,
                    restitution: 0.3,
                    friction: 0.01,
                    render:{
                      fillStyle: fillColor,
                      strokeStyle: '#ffffff'
                    }
                });
              }

              // TODO - track color to correlate object body with data on right

              scope.world.add(scope.engine.world, body);
              $log.debug(scope.engine.world.bodies.length - 4); // don't count borders
              scope.messages.push({id: scope.engine.world.bodies.length - 4, text: 'This is just random text over and over', color:fillColor});
              $timeout(function(){
                $('#messageDrop')[0].scrollTop = 0;
              }, 500);
            });

          }
        };
    }
})();<commit_msg>code cleanup<commit_after>(function(){
    'use strict';

    /**
     * @ngdoc directive
     * @name upBoardApp.directive:ubDropIn
     * @description
     * # dropIn
     */
    angular.module('upBoardApp')
      .directive('ubDropIn', dropIn);

    dropIn.$inject = ['$log', '$timeout', '$document', 'utility'];
    function dropIn($log, $timeout, $document, utility) {
        return {
          templateUrl: '/app/frontend/scripts/directives/ub-drop-in.tpl.html',
          restrict: 'E',
          replace: true,
          scope:{
              data: '='
          },
          link: function(scope, element, attrs){

              var savedData; // use to remember for exit transition
              scope.messages = [];

              // Matter module aliases
              var Engine = Matter.Engine,
                  World = Matter.World,
                  //Body = Matter.Body,
                  Bodies = Matter.Bodies,
                  Common = Matter.Common,
                  //Composite = Matter.Composite,
                  //Composites = Matter.Composites,
                  //Events = Matter.Events,
                  MouseConstraint = Matter.MouseConstraint;

              // extend base Render class, modify how body ids are displayed
              var CustomRender = {};
              angular.extend(CustomRender, Matter.Render);

              CustomRender.bodyIds = function(engine, bodies, context) {
                  var c = context,
                      i,
                      j;

                  for (i = 0; i < bodies.length; i++) {
                      if (!bodies[i].render.visible)
                          continue;

                      var parts = bodies[i].parts;
                      for (j = parts.length > 1 ? 1 : 0; j < parts.length; j++) {
                          var part = parts[j];
                          c.font = '14px Helvetica';
                          c.textAlign = 'center';
                          c.fillStyle = 'rgba(255,255,255,1)';
                          c.fillText(part.id - 5, part.position.x, part.position.y);
                      }
                  }
              };

              // create a Matter.js engine, use CustomRender
              var engine = Engine.create(document.getElementById('dropInPoint'), {
                render: {
                  controller: CustomRender,
                  options: {
                    showAngleIndicator: false,
                    wireframes: false,
                    background: '#ffffff',
                    showIds: true
                  }
                }
              });

              // add a mouse controlled constraint
              var mouseConstraint = MouseConstraint.create(engine);
              World.add(engine.world, mouseConstraint);

              var offset = 0,
                  options = {
                      isStatic: true,
                      render: {
                          visible: false
                      }
                  };

              engine.world.bodies = [];

              // static walls will not be rendered due to options setting
              World.add(engine.world, [
                  Bodies.rectangle(400, -offset, 800.5 + 2 * offset, 5, options),
                  Bodies.rectangle(400, 600 + offset, 800.5 + 2 * offset, 5, options),
                  Bodies.rectangle(800 + offset, 300, 5, 600.5 + 2 * offset, options),
                  Bodies.rectangle(-offset, 300, 5, 600.5 + 2 * offset, options)
              ]);

              scope.engine = engine;
              scope.world = World;
              scope.bodies = Bodies;

              // run the engine
              Engine.run(engine);

              scope.$watch(attrs.ngShow, function(){

                var isShown = scope.$eval(attrs.ngShow);

                if(isShown){
                  $log.debug('about to show ' + scope.data.type);
                  utility.setEntryTransition(element, scope.data);
                  savedData = scope.data;
                }
                else if(savedData != null){
                  $log.debug('about to hide ' + savedData.type + ', next type on deck: ' + scope.data.type);
                  utility.setExitTransition(element, savedData);
                  savedData = null;
                }
              });

            $document.on('click', function(event){

              var x = 400, y = 0;
              var body;

              var colorArr = ['#CF4858','#F6624A','#1B6A81','#16A79D',
                              '#80628B','#DC557A','#F4AC42','#4F8598','#69B1CB'];

              var fillColor = Common.choose(colorArr);

              // TODO add another random polygon
              if (Common.random() > 0.35) {
                var randDim = Common.random(50, 70);
                body = Bodies.rectangle(x, y, randDim, randDim,{
                  render:{
                    fillStyle: fillColor,
                    strokeStyle: '#ffffff'
                  }
                });
              }
              else {
                body = Bodies.circle(x, y, Common.random(30,50), {
                    density: 0.0005,
                    frictionAir: 0.06,
                    restitution: 0.3,
                    friction: 0.01,
                    render:{
                      fillStyle: fillColor,
                      strokeStyle: '#ffffff'
                    }
                });
              }

              scope.world.add(scope.engine.world, body);
              $log.debug(scope.engine.world.bodies.length - 4); // don't count borders
              scope.messages.push({id: scope.engine.world.bodies.length - 4,
                                   text: 'This is just random text over and over',
                                   color:fillColor});
              $timeout(function(){
                $('#messageDrop')[0].scrollTop = 0;
              }, 500);
            });

          }
        };
    }
})();<|endoftext|>


<commit_before>/**
  * @constructor
  * @extends BaseEditor
  */

var DOMMarkupEditor = function()
{
  this.base_init(this);
  this.type = "dom-markup-editor";
  this.domnodeserializer = new cls.DOMSerializer();
  // specific context
  this.context_enter =
  {
    rt_id: '',
    obj_id: '',
    parent_obj_id: '',
    outerHTML: '',
    host_target: ''
  }

  this.context_cur =
  {
    rt_id: '',
    obj_id: '',
    parent_obj_id: '',
    outerHTML: '',
    host_target: ''
  }

  const
  STATUS = 0,
  OBJECT_VALUE = 3,
  OBJECT_ID = 0,
  NODE_LIST = 0;

  this.edit = function(event, ref_ele)
  {
    var
    ele = ref_ele || event.target,
    rt_id = parseInt(ele.get_attr('parent-node-chain', 'rt-id')),
    obj_id = parseInt(ele.parentElement.getAttribute('ref-id')),
    model_id = ele.get_attr('parent-node-chain', 'data-model-id'),
    script = '',
    tag = '',
    prop = '',
    container = ele,
    model = null,
    dom = null,
    cb = null;

    while( container
            && !/container/i.test(container.nodeName)
            && ( container = container.parentElement ) );
    if(container && model_id)
    {
      model = window.dominspections[model_id];
      this.context_enter =
      {
        rt_id: rt_id,
        obj_id: obj_id,
        parent_obj_id: model.getParentElement(obj_id),
        outerHTML: '',
        host_target: '',
        model: model
      };
      this.context_cur = {};
      for( prop in this.context_enter )
      {
        this.context_cur[prop] = this.context_enter[prop];
      }
      script = this["return new Host_updater(target)"];
      tag = tagManager.set_callback(this, this.register_host_updater, [rt_id]);
      services['ecmascript-debugger'].requestEval(tag, [rt_id, 0, 0, script, [['target', obj_id]]]);
      dom = new cls.InspectableDOMNode(rt_id, obj_id);
      cb = this.handle_get_outer_html.bind(this, dom, rt_id, obj_id, ele, event);
      dom.expand(cb, obj_id, "subtree");
    }
  }

  this.oninput = function(event)
  {

    var
    script = "",
    state = this.context_cur;

    if( this.textarea_container.parentElement && state.host_target )
    {
      this.set_textarea_dimensions();
      state.outerHTML = this.textarea.value;
      var script = "host_target.outerHTML = '" + encode(state.outerHTML) + "';";
      services['ecmascript-debugger'].requestEval
      (
        0, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
      );
    }
  }

  this.submit = function()
  {
    // return a valid navigation target or null
    var
    state = this.context_cur,
    nav_target = this.textarea_container.parentElement;

    if(nav_target)
    {
      var tag = tagManager.set_callback(this, this.on_exit_edit, [state, nav_target]);
      var script = "host_target.exit_edit()";
      services['ecmascript-debugger'].requestEval
      (
        tag, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
      );
    }
    return nav_target;
  }

  this.cancel = function()
  {
    // return a valid navigation target or null.
    // this is aproblem. the view will be updated anyway.
    var
    script = "",
    state = this.context_enter,
    nav_target = this.textarea_container.parentElement;

    if( nav_target )
    {
      if(state.outerHTML != this.context_cur.outerHTML)
      {
        var tag = tagManager.set_callback(this, this.on_exit_edit, [state, nav_target]);
        var script = "host_target.cancel_edit()";
        services['ecmascript-debugger'].requestEval
        (
          tag, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
        );
      }
      else
      {
        nav_target.textContent = "";
        this.context_cur = this.context_enter = null;
        views.dom.update();
      }
    }
    return nav_target;
  }

  this.nav_next = function()
  {
    var
    val = this.textarea.value,
    start = this.textarea.selectionStart,
    end = this.textarea.selectionEnd,
    indent = '  ';

    this.textarea.value = val.slice(0, start) + indent +  val.slice(end);
    this.textarea.selectionStart = start + indent.length;
    this.textarea.selectionEnd = start + indent.length;
  }

  this.set_textarea_dimensions = function()
  {
    this.textarea.style.height = this.textarea.scrollHeight + 'px';
  }

  this.__is_active = function()
  {
    return this.context_enter && this.context_enter.host_target && true || false;
  }

  // could be the default method?
  this.onclick = function(event)
  {
    if (!this.textarea_container.contains(event.target))
    {
      this.submit(true);
      return false;
    }
    return true;
  }

  // helpers

  this.on_exit_edit = function(status, message, state, nav_target)
  {
    if( message[STATUS] == 'completed' )
    {
      var scroll_position = new Element.ScrollPosition(nav_target);
      var cb = function()
      {
        nav_target.textContent = "";
        window.views.dom.update();
        scroll_position.reset(document.getElementById('target-element'));
      }
      this.context_enter.model.collapse(state.parent_obj_id);
      this.context_enter.model.expand(cb, state.parent_obj_id, 'children');
      this.context_cur = this.context_enter = null;
    }
    else
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        'exit markup edit failed in DOMMarkupEditor');
    }
  };

  var encode = function(str)
  {
    return str.replace(/\r\n/g, "\\n").replace(/'/g, "\\'");
  };

  // class on the host side to update the given DOM range
  var Host_updater = function(target)
  {
    var
    range_target = document.createRange(),
    timeout = 0,
    new_str = '',
    enter_node = null,
    namespace = target.namespaceURI,
    scripts = null,
    script = null,
    script_type_attr = '',
    i = 0,
    update = function(str)
    {
      if( enter_node )
      {
        range_target.deleteContents();
      }
      else
      {
        enter_node = range_target.extractContents();
      };
      var range_source = document.createRange();
      var temp = null;
      if( document.documentElement )
      {
        temp = namespace
                ? document.createElementNS(namespace, 'df-temp-element')
                : document.createElement('df-temp-element');
        ( document.body || document.documentElement ).appendChild(temp);
        if(typeof temp.innerHTML == 'string')
        {
          temp.innerHTML = new_str;
        }
        else
        {
          innerXML(temp, new_str)
        }
      }
      else
      {
        var new_source = lexer.lex(new_str), tag = new_source.length && new_source[0];

        if(tag)
        {
          var attrs = tag[2], i = 0, attr = null;
          for( ; ( attr = attrs[i] ) && attr[0] != 'xmlns'; i++);
          if( attr )
          {
            attrs.splice(i, 1);
            temp = document.createElementNS(attr[1], tag[1]);
          }
          else
          {
            temp = document.createElement(tag[1]);
          }
          for( i = 0 ; attr = attrs[i]; i++)
          {
            temp.setAttribute(attr[0], attr[1]);
          }
          document.appendChild(temp);
          if(typeof temp.innerHTML == 'string')
          {
            temp.innerHTML = new_source[1];
          }
          else
          {
            innerXML(temp, new_source[1].replace(new RegExp('</' + tag[1] + '>$'), ''))
          }
          range_target.selectNode(document.documentElement);
          temp = null;
        }
      };
      for( scripts = (temp || document).getElementsByTagName('script'), i = 0; script = scripts[i]; i++)
      {
        script_attr = script.getAttribute('type');
        script.setAttribute('type', ( script_attr ? script_attr + '/' : '' ) + 'edited');
      }
      var first =  temp && temp.firstChild;
      var last = temp && temp.lastChild;
      if(first)
      {
        range_source.selectNodeContents(temp);
        var fragment = range_source.extractContents();
        range_target.insertNode(fragment);
        range_target.setStartBefore(first);
        range_target.setEndAfter(last);

      };
      if(temp && temp.parentElement)
      {
        temp.parentNode.removeChild(temp);
      };
      timeout = 0;
    },
    lexer = new function()
    {
      /* adjusted to get only the first tag */
      const TEXT = 'TEXT', TAG = 'TAG';
      var
      source = '',
      cur = '',
      pos = 0,
      text = function()
      {
        var ret = [], buffer = '', _tag = null;
        while( cur )
        {
          if( cur == '<' )
          {
            cur = source.charAt(pos++);
            if(buffer)
            {
              ret[ret.length] = [TEXT, buffer];
              buffer = '';
            }
            _tag = tag();
            if( _tag && !/[!?]/.test(_tag[1].charAt(0)) )
            {
              return [_tag, source.slice(pos-1)];
            }
            continue;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
        return ret;
      },
      tag = function()
      {
        var buffer = '', attrs = [], has_attrs = false;
        while( cur )
        {
          if( cur == '>' )
          {
            cur = source.charAt(pos++);
            return [TAG, buffer, attrs];
          }
          if( cur == ' ' )
          {
            cur = source.charAt(pos++);
            has_attrs = true;
            continue;
          }
          if( has_attrs )
          {
            attrs = attr_key();
            continue;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      },
      attr_key = function()
      {
        var attrs = [], attr_key = '', buffer = '';
        while( cur )
        {
          if( cur == ' ' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            cur = source.charAt(pos++);
            continue;
          }
          if( cur == '=' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            cur = source.charAt(pos++);
            attrs[attrs.length] = [attr_key, set_attr_delimiter()];
            attr_key = '';
            continue;
          }
          if( cur == '>' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            if(attr_key)
            {
              attrs[attrs.length] = [attr_key];
            }
            return attrs;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      },
      set_attr_delimiter = function()
      {
        var delimiter = '';
        while( cur )
        {
          if( cur == ' ' )
          {
            cur = source.charAt(pos++);
            continue;
          }
          if( cur == '\'' || cur == '"' )
          {
            delimiter = cur;
            cur = source.charAt(pos++);
            return attr_value(delimiter);
          }
          return attr_value(' ');
        }
      },
      attr_value = function(delimiter)
      {
        var buffer = '';
        while(cur)
        {
          if( cur == delimiter )
          {
            cur = source.charAt(pos++);
            return buffer;
          }
          if( cur == '>' && delimiter == ' ' )
          {
            return buffer;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      };

      this.lex = function(_source)
      {
        source = _source;
        pos = 0;
        cur = source.charAt(pos++);
        return text();
      }
    },
    innerXML = function(ele, str)
    {
      var
      parser = new DOMParser(),
      source = "<temp-root xmlns=\"" + namespace + "\">" + str + "</temp-root>",
      doc = parser.parseFromString(source, "application/xml");

      if( doc.documentElement.nodeName.toLowerCase() == "temp-root" )
      {
        range = doc.createRange();
        range.selectNodeContents(doc.documentElement);
        ele.appendChild(range.extractContents());
      }
      else
      {
        ele.textContent = "<parseerror/>";
      }
    };

    range_target.selectNode(target);
    this.__defineSetter__
    (
      "outerHTML",
      function(str)
      {
        new_str = str;
        timeout || ( timeout = setTimeout(update, 100) );
      }
    );
    this.cancel_edit = function()
    {
      timeout && ( timeout = clearTimeout(timeout) );
      if(enter_node)
      {
        range_target.deleteContents();
        range_target.insertNode(enter_node);
      }
      this.exit_edit();
    };
    this.exit_edit = function()
    {
      if( timeout )
      {
        timeout = clearTimeout(timeout);
        update();
      };
      lexer = range_target = timeout = new_str = enter_node = update = null;
    };
  };

  this["return new Host_updater(target)"] =
    "return new (" + Host_updater.toString() + ")(target)";

  this.register_host_updater = function(status, message, rt_id)
  {
    var
    status = message[STATUS],
    obj_id = message[OBJECT_VALUE][OBJECT_ID];

    if(  status == 'completed' && obj_id )
    {
      this.context_enter.host_target = this.context_cur.host_target = obj_id;
    }
    else
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        "failed register_host_updater in DOMMarkupEditor");
    }
  }

  // complete the edit call
  this.handle_get_outer_html = function(dom, rt_id, obj_id, ele, event)
  {
    var
    outerHTML = this.domnodeserializer.serialize(dom),
    parent = ele.parentNode,
    parent_parent = parent.parentElement,
    margin = parseInt(parent.style.marginLeft),
    next = null;
    this.context_enter.outerHTML = this.context_cur.outerHTML = outerHTML;
    if( !this.base_style['font-size'] )
    {
      this.get_base_style(ele);
    }
    // this should never be needed
    if( this.textarea_container.parentElement )
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        "this.textarea_container.parentElement is not null in submit");
    }
    this.textarea.value = outerHTML;
    var scroll_position = new Element.ScrollPosition(parent);
    parent.innerHTML = "";
    parent.appendChild(this.textarea_container);
    while( ( next = parent.nextElementSibling ) && parseInt(next.style.marginLeft) > margin )
    {
      parent_parent.removeChild(next);
    };
    if( next && parseInt(next.style.marginLeft) == margin && /<\//.test(next.textContent) )
    {
      parent_parent.removeChild(next);
    }
    this.set_textarea_dimensions();
    // only for click events
    if( event )
    {
      this.textarea.focus();
    }
    this.textarea.selectionEnd = this.textarea.selectionStart = 0;
    scroll_position.reset();
  }
}

DOMMarkupEditor.prototype = BaseEditor;<commit_msg>Fix for DFL-1569, Editing a path element adds df-temp-element elements.<commit_after>/**
  * @constructor
  * @extends BaseEditor
  */

var DOMMarkupEditor = function()
{
  this.base_init(this);
  this.type = "dom-markup-editor";
  this.domnodeserializer = new cls.DOMSerializer();
  // specific context
  this.context_enter =
  {
    rt_id: '',
    obj_id: '',
    parent_obj_id: '',
    outerHTML: '',
    host_target: ''
  }

  this.context_cur =
  {
    rt_id: '',
    obj_id: '',
    parent_obj_id: '',
    outerHTML: '',
    host_target: ''
  }

  const
  STATUS = 0,
  OBJECT_VALUE = 3,
  OBJECT_ID = 0,
  NODE_LIST = 0;

  this.edit = function(event, ref_ele)
  {
    var
    ele = ref_ele || event.target,
    rt_id = parseInt(ele.get_attr('parent-node-chain', 'rt-id')),
    obj_id = parseInt(ele.parentElement.getAttribute('ref-id')),
    model_id = ele.get_attr('parent-node-chain', 'data-model-id'),
    script = '',
    tag = '',
    prop = '',
    container = ele,
    model = null,
    dom = null,
    cb = null;

    while( container
            && !/container/i.test(container.nodeName)
            && ( container = container.parentElement ) );
    if(container && model_id)
    {
      model = window.dominspections[model_id];
      this.context_enter =
      {
        rt_id: rt_id,
        obj_id: obj_id,
        parent_obj_id: model.getParentElement(obj_id),
        outerHTML: '',
        host_target: '',
        model: model
      };
      this.context_cur = {};
      for( prop in this.context_enter )
      {
        this.context_cur[prop] = this.context_enter[prop];
      }
      script = this["return new Host_updater(target)"];
      tag = tagManager.set_callback(this, this.register_host_updater, [rt_id]);
      services['ecmascript-debugger'].requestEval(tag, [rt_id, 0, 0, script, [['target', obj_id]]]);
      dom = new cls.InspectableDOMNode(rt_id, obj_id);
      cb = this.handle_get_outer_html.bind(this, dom, rt_id, obj_id, ele, event);
      dom.expand(cb, obj_id, "subtree");
    }
  }

  this.oninput = function(event)
  {

    var
    script = "",
    state = this.context_cur;

    if( this.textarea_container.parentElement && state.host_target )
    {
      this.set_textarea_dimensions();
      state.outerHTML = this.textarea.value;
      var script = "host_target.outerHTML = '" + encode(state.outerHTML) + "';";
      services['ecmascript-debugger'].requestEval
      (
        0, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
      );
    }
  }

  this.submit = function()
  {
    // return a valid navigation target or null
    var
    state = this.context_cur,
    nav_target = this.textarea_container.parentElement;

    if(nav_target)
    {
      var tag = tagManager.set_callback(this, this.on_exit_edit, [state, nav_target]);
      var script = "host_target.exit_edit()";
      services['ecmascript-debugger'].requestEval
      (
        tag, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
      );
    }
    return nav_target;
  }

  this.cancel = function()
  {
    // return a valid navigation target or null.
    // this is aproblem. the view will be updated anyway.
    var
    script = "",
    state = this.context_enter,
    nav_target = this.textarea_container.parentElement;

    if( nav_target )
    {
      if(state.outerHTML != this.context_cur.outerHTML)
      {
        var tag = tagManager.set_callback(this, this.on_exit_edit, [state, nav_target]);
        var script = "host_target.cancel_edit()";
        services['ecmascript-debugger'].requestEval
        (
          tag, [state.rt_id, 0, 0, script, [['host_target', state.host_target]]]
        );
      }
      else
      {
        nav_target.textContent = "";
        this.context_cur = this.context_enter = null;
        views.dom.update();
      }
    }
    return nav_target;
  }

  this.nav_next = function()
  {
    var
    val = this.textarea.value,
    start = this.textarea.selectionStart,
    end = this.textarea.selectionEnd,
    indent = '  ';

    this.textarea.value = val.slice(0, start) + indent +  val.slice(end);
    this.textarea.selectionStart = start + indent.length;
    this.textarea.selectionEnd = start + indent.length;
  }

  this.set_textarea_dimensions = function()
  {
    this.textarea.style.height = this.textarea.scrollHeight + 'px';
  }

  this.__is_active = function()
  {
    return this.context_enter && this.context_enter.host_target && true || false;
  }

  // could be the default method?
  this.onclick = function(event)
  {
    if (!this.textarea_container.contains(event.target))
    {
      this.submit(true);
      return false;
    }
    return true;
  }

  // helpers

  this.on_exit_edit = function(status, message, state, nav_target)
  {
    if( message[STATUS] == 'completed' )
    {
      var scroll_position = new Element.ScrollPosition(nav_target);
      var cb = function()
      {
        nav_target.textContent = "";
        window.views.dom.update();
        scroll_position.reset(document.getElementById('target-element'));
      }
      this.context_enter.model.collapse(state.parent_obj_id);
      this.context_enter.model.expand(cb, state.parent_obj_id, 'children');
      this.context_cur = this.context_enter = null;
    }
    else
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        'exit markup edit failed in DOMMarkupEditor');
    }
  };

  var encode = function(str)
  {
    return str.replace(/\r\n/g, "\\n").replace(/'/g, "\\'");
  };

  // class on the host side to update the given DOM range
  var Host_updater = function(target)
  {
    var
    range_target = document.createRange(),
    timeout = 0,
    new_str = '',
    enter_node = null,
    namespace = target.namespaceURI,
    scripts = null,
    script = null,
    script_type_attr = '',
    i = 0,
    update = function(str)
    {
      if( enter_node )
      {
        range_target.deleteContents();
      }
      else
      {
        enter_node = range_target.extractContents();
      };
      var range_source = document.createRange();
      var temp = null;
      if( document.documentElement )
      {
        temp = namespace
                ? document.createElementNS(namespace, 'df-temp-element')
                : document.createElement('df-temp-element');
        ( document.body || document.documentElement ).appendChild(temp);
        if(typeof temp.innerHTML == 'string')
        {
          temp.innerHTML = new_str;
        }
        else
        {
          innerXML(temp, new_str)
        }
      }
      else
      {
        var new_source = lexer.lex(new_str), tag = new_source.length && new_source[0];

        if(tag)
        {
          var attrs = tag[2], i = 0, attr = null;
          for( ; ( attr = attrs[i] ) && attr[0] != 'xmlns'; i++);
          if( attr )
          {
            attrs.splice(i, 1);
            temp = document.createElementNS(attr[1], tag[1]);
          }
          else
          {
            temp = document.createElement(tag[1]);
          }
          for( i = 0 ; attr = attrs[i]; i++)
          {
            temp.setAttribute(attr[0], attr[1]);
          }
          document.appendChild(temp);
          if(typeof temp.innerHTML == 'string')
          {
            temp.innerHTML = new_source[1];
          }
          else
          {
            innerXML(temp, new_source[1].replace(new RegExp('</' + tag[1] + '>$'), ''))
          }
          range_target.selectNode(document.documentElement);
          temp = null;
        }
      };
      for( scripts = (temp || document).getElementsByTagName('script'), i = 0; script = scripts[i]; i++)
      {
        script_attr = script.getAttribute('type');
        script.setAttribute('type', ( script_attr ? script_attr + '/' : '' ) + 'edited');
      }
      var first =  temp && temp.firstChild;
      var last = temp && temp.lastChild;
      if(first)
      {
        range_source.selectNodeContents(temp);
        var fragment = range_source.extractContents();
        range_target.insertNode(fragment);
        range_target.setStartBefore(first);
        range_target.setEndAfter(last);

      };
      clean_up_temp();
      timeout = 0;
    },
    lexer = new function()
    {
      /* adjusted to get only the first tag */
      const TEXT = 'TEXT', TAG = 'TAG';
      var
      source = '',
      cur = '',
      pos = 0,
      text = function()
      {
        var ret = [], buffer = '', _tag = null;
        while( cur )
        {
          if( cur == '<' )
          {
            cur = source.charAt(pos++);
            if(buffer)
            {
              ret[ret.length] = [TEXT, buffer];
              buffer = '';
            }
            _tag = tag();
            if( _tag && !/[!?]/.test(_tag[1].charAt(0)) )
            {
              return [_tag, source.slice(pos-1)];
            }
            continue;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
        return ret;
      },
      tag = function()
      {
        var buffer = '', attrs = [], has_attrs = false;
        while( cur )
        {
          if( cur == '>' )
          {
            cur = source.charAt(pos++);
            return [TAG, buffer, attrs];
          }
          if( cur == ' ' )
          {
            cur = source.charAt(pos++);
            has_attrs = true;
            continue;
          }
          if( has_attrs )
          {
            attrs = attr_key();
            continue;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      },
      attr_key = function()
      {
        var attrs = [], attr_key = '', buffer = '';
        while( cur )
        {
          if( cur == ' ' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            cur = source.charAt(pos++);
            continue;
          }
          if( cur == '=' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            cur = source.charAt(pos++);
            attrs[attrs.length] = [attr_key, set_attr_delimiter()];
            attr_key = '';
            continue;
          }
          if( cur == '>' )
          {
            if(buffer)
            {
              attr_key = buffer;
              buffer = '';
            }
            if(attr_key)
            {
              attrs[attrs.length] = [attr_key];
            }
            return attrs;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      },
      set_attr_delimiter = function()
      {
        var delimiter = '';
        while( cur )
        {
          if( cur == ' ' )
          {
            cur = source.charAt(pos++);
            continue;
          }
          if( cur == '\'' || cur == '"' )
          {
            delimiter = cur;
            cur = source.charAt(pos++);
            return attr_value(delimiter);
          }
          return attr_value(' ');
        }
      },
      attr_value = function(delimiter)
      {
        var buffer = '';
        while(cur)
        {
          if( cur == delimiter )
          {
            cur = source.charAt(pos++);
            return buffer;
          }
          if( cur == '>' && delimiter == ' ' )
          {
            return buffer;
          }
          buffer += cur;
          cur = source.charAt(pos++);
        }
      };

      this.lex = function(_source)
      {
        source = _source;
        pos = 0;
        cur = source.charAt(pos++);
        return text();
      }
    },
    innerXML = function(ele, str)
    {
      var
      parser = new DOMParser(),
      source = "<temp-root xmlns=\"" + namespace + "\">" + str + "</temp-root>",
      doc = parser.parseFromString(source, "application/xml");

      if( doc.documentElement.nodeName.toLowerCase() == "temp-root" )
      {
        range = doc.createRange();
        range.selectNodeContents(doc.documentElement);
        ele.appendChild(range.extractContents());
      }
      else
      {
        ele.textContent = "<parseerror/>";
      }
    },
    clean_up_temp = function()
    {
      var temp = null;
      while (temp = document.getElementsByTagName('df-temp-element')[0])
      {
        temp.parentNode.removeChild(temp);
      }
    };

    range_target.selectNode(target);
    this.__defineSetter__
    (
      "outerHTML",
      function(str)
      {
        new_str = str;
        timeout || ( timeout = setTimeout(update, 100) );
      }
    );
    this.cancel_edit = function()
    {
      timeout && ( timeout = clearTimeout(timeout) );
      if(enter_node)
      {
        range_target.deleteContents();
        range_target.insertNode(enter_node);
      }
      this.exit_edit();
    };
    this.exit_edit = function()
    {
      if( timeout )
      {
        timeout = clearTimeout(timeout);
        update();
      };
      clean_up_temp();
      lexer = range_target = timeout = new_str = enter_node = update = null;
    };
  };

  this["return new Host_updater(target)"] =
    "return new (" + Host_updater.toString() + ")(target)";

  this.register_host_updater = function(status, message, rt_id)
  {
    var
    status = message[STATUS],
    obj_id = message[OBJECT_VALUE][OBJECT_ID];

    if(  status == 'completed' && obj_id )
    {
      this.context_enter.host_target = this.context_cur.host_target = obj_id;
    }
    else
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        "failed register_host_updater in DOMMarkupEditor");
    }
  }

  // complete the edit call
  this.handle_get_outer_html = function(dom, rt_id, obj_id, ele, event)
  {
    var
    outerHTML = this.domnodeserializer.serialize(dom),
    parent = ele.parentNode,
    parent_parent = parent.parentElement,
    margin = parseInt(parent.style.marginLeft),
    next = null;
    this.context_enter.outerHTML = this.context_cur.outerHTML = outerHTML;
    if( !this.base_style['font-size'] )
    {
      this.get_base_style(ele);
    }
    // this should never be needed
    if( this.textarea_container.parentElement )
    {
      opera.postError(ui_strings.DRAGONFLY_INFO_MESSAGE +
        "this.textarea_container.parentElement is not null in submit");
    }
    this.textarea.value = outerHTML;
    var scroll_position = new Element.ScrollPosition(parent);
    parent.innerHTML = "";
    parent.appendChild(this.textarea_container);
    while( ( next = parent.nextElementSibling ) && parseInt(next.style.marginLeft) > margin )
    {
      parent_parent.removeChild(next);
    };
    if( next && parseInt(next.style.marginLeft) == margin && /<\//.test(next.textContent) )
    {
      parent_parent.removeChild(next);
    }
    this.set_textarea_dimensions();
    // only for click events
    if( event )
    {
      this.textarea.focus();
    }
    this.textarea.selectionEnd = this.textarea.selectionStart = 0;
    scroll_position.reset();
  }
}

DOMMarkupEditor.prototype = BaseEditor;<|endoftext|>


<commit_before>import {
  TIMELINE_UPDATE,
  TIMELINE_DELETE,
  TIMELINE_CLEAR,
  TIMELINE_EXPAND_SUCCESS,
  TIMELINE_EXPAND_REQUEST,
  TIMELINE_EXPAND_FAIL,
  TIMELINE_SCROLL_TOP,
  TIMELINE_CONNECT,
  TIMELINE_DISCONNECT,
  TIMELINE_LOAD_PENDING,
  TIMELINE_MARK_AS_PARTIAL,
} from '../actions/timelines';
import {
  ACCOUNT_BLOCK_SUCCESS,
  ACCOUNT_MUTE_SUCCESS,
  ACCOUNT_UNFOLLOW_SUCCESS,
} from '../actions/accounts';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import compareId from '../compare_id';

const initialState = ImmutableMap();

const initialTimeline = ImmutableMap({
  unread: 0,
  online: false,
  top: true,
  isLoading: false,
  hasMore: true,
  pendingItems: ImmutableList(),
  items: ImmutableList(),
});

const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => {
  // This method is pretty tricky because:
  // - existing items in the timeline might be out of order
  // - the existing timeline may have gaps, most often explicitly noted with a `null` item
  // - ideally, we don't want it to reorder existing items of the timeline
  // - `statuses` may include items that are already included in the timeline
  // - this function can be called either to fill in a gap, or load newer items

  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    mMap.set('isLoading', false);
    mMap.set('isPartial', isPartial);

    if (!next && !isLoadingRecent) mMap.set('hasMore', false);

    if (timeline.endsWith(':pinned')) {
      mMap.set('items', statuses.map(status => status.get('id')));
    } else if (!statuses.isEmpty()) {
      usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('pendingItems').isEmpty());

      mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
        const newIds = statuses.map(status => status.get('id'));

        // Now this gets tricky, as we don't necessarily know for sure where the gap to fill is
        // and some items in the timeline may not be properly ordered.

        // However, we know that `newIds.last()` is the oldest item that was requested and that
        // there is no “hole” between `newIds.last()` and `newIds.first()`.

        // First, find the furthest (if properly sorted, oldest) item in the timeline that is
        // newer than the oldest fetched one, as it's most likely that it delimits the gap.
        // Start the gap *after* that item.
        const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;

        // Then, try to find the furthest (if properly sorted, oldest) item in the timeline that
        // is newer than the most recent fetched one, as it delimits a section comprised of only
        // items present in `newIds` (or that were deleted from the server, so should be removed
        // anyway).
        // Stop the gap *after* that item.
        const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0) + 1;

        // Make sure we aren't inserting duplicates
        let insertedIds = ImmutableOrderedSet(newIds).subtract(oldIds.take(firstIndex), oldIds.skip(lastIndex)).toList();
        // Finally, insert a gap marker if the data is marked as partial by the server
        if (isPartial && (firstIndex === 0 || oldIds.get(firstIndex - 1) !== null)) {
          insertedIds = insertedIds.unshift(null);
        }

        return oldIds.take(firstIndex).concat(
          insertedIds,
          oldIds.skip(lastIndex),
        );
      });
    }
  }));
};

const updateTimeline = (state, timeline, status, usePendingItems) => {
  const top = state.getIn([timeline, 'top']);

  if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
    if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
      return state;
    }

    return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1));
  }

  const ids        = state.getIn([timeline, 'items'], ImmutableList());
  const includesId = ids.includes(status.get('id'));
  const unread     = state.getIn([timeline, 'unread'], 0);

  if (includesId) {
    return state;
  }

  let newIds = ids;

  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    if (!top) mMap.set('unread', unread + 1);
    if (top && ids.size > 40) newIds = newIds.take(20);
    mMap.set('items', newIds.unshift(status.get('id')));
  }));
};

const deleteStatus = (state, id, references, exclude_account = null) => {
  state.keySeq().forEach(timeline => {
    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
      const helper = list => list.filterNot(item => item === id);
      state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper);
    }
  });

  // Remove reblogs of deleted status
  references.forEach(ref => {
    state = deleteStatus(state, ref, [], exclude_account);
  });

  return state;
};

const clearTimeline = (state, timeline) => {
  return state.set(timeline, initialTimeline);
};

const filterTimelines = (state, relationship, statuses) => {
  let references;

  statuses.forEach(status => {
    if (status.get('account') !== relationship.id) {
      return;
    }

    references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
    state      = deleteStatus(state, status.get('id'), references, relationship.id);
  });

  return state;
};

const filterTimeline = (timeline, state, relationship, statuses) => {
  const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id);
  return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper);
};

const updateTop = (state, timeline, top) => {
  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    if (top) mMap.set('unread', mMap.get('pendingItems').size);
    mMap.set('top', top);
  }));
};

export default function timelines(state = initialState, action) {
  switch(action.type) {
  case TIMELINE_LOAD_PENDING:
    return state.update(action.timeline, initialTimeline, map =>
      map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0));
  case TIMELINE_EXPAND_REQUEST:
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
  case TIMELINE_EXPAND_FAIL:
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
  case TIMELINE_EXPAND_SUCCESS:
    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
  case TIMELINE_UPDATE:
    return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
  case TIMELINE_DELETE:
    return deleteStatus(state, action.id, action.references, action.reblogOf);
  case TIMELINE_CLEAR:
    return clearTimeline(state, action.timeline);
  case ACCOUNT_BLOCK_SUCCESS:
  case ACCOUNT_MUTE_SUCCESS:
    return filterTimelines(state, action.relationship, action.statuses);
  case ACCOUNT_UNFOLLOW_SUCCESS:
    return filterTimeline('home', state, action.relationship, action.statuses);
  case TIMELINE_SCROLL_TOP:
    return updateTop(state, action.timeline, action.top);
  case TIMELINE_CONNECT:
    return state.update(action.timeline, initialTimeline, map => map.set('online', true));
  case TIMELINE_DISCONNECT:
    return state.update(
      action.timeline,
      initialTimeline,
      map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items),
    );
  case TIMELINE_MARK_AS_PARTIAL:
    return state.update(
      action.timeline,
      initialTimeline,
      map => map.set('isPartial', true).set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('unread', 0),
    );
  default:
    return state;
  }
};
<commit_msg>Fix older items possibly disappearing on timeline updates (#17980)<commit_after>import {
  TIMELINE_UPDATE,
  TIMELINE_DELETE,
  TIMELINE_CLEAR,
  TIMELINE_EXPAND_SUCCESS,
  TIMELINE_EXPAND_REQUEST,
  TIMELINE_EXPAND_FAIL,
  TIMELINE_SCROLL_TOP,
  TIMELINE_CONNECT,
  TIMELINE_DISCONNECT,
  TIMELINE_LOAD_PENDING,
  TIMELINE_MARK_AS_PARTIAL,
} from '../actions/timelines';
import {
  ACCOUNT_BLOCK_SUCCESS,
  ACCOUNT_MUTE_SUCCESS,
  ACCOUNT_UNFOLLOW_SUCCESS,
} from '../actions/accounts';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import compareId from '../compare_id';

const initialState = ImmutableMap();

const initialTimeline = ImmutableMap({
  unread: 0,
  online: false,
  top: true,
  isLoading: false,
  hasMore: true,
  pendingItems: ImmutableList(),
  items: ImmutableList(),
});

const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent, usePendingItems) => {
  // This method is pretty tricky because:
  // - existing items in the timeline might be out of order
  // - the existing timeline may have gaps, most often explicitly noted with a `null` item
  // - ideally, we don't want it to reorder existing items of the timeline
  // - `statuses` may include items that are already included in the timeline
  // - this function can be called either to fill in a gap, or load newer items

  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    mMap.set('isLoading', false);
    mMap.set('isPartial', isPartial);

    if (!next && !isLoadingRecent) mMap.set('hasMore', false);

    if (timeline.endsWith(':pinned')) {
      mMap.set('items', statuses.map(status => status.get('id')));
    } else if (!statuses.isEmpty()) {
      usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('pendingItems').isEmpty());

      mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
        const newIds = statuses.map(status => status.get('id'));

        // Now this gets tricky, as we don't necessarily know for sure where the gap to fill is
        // and some items in the timeline may not be properly ordered.

        // However, we know that `newIds.last()` is the oldest item that was requested and that
        // there is no “hole” between `newIds.last()` and `newIds.first()`.

        // First, find the furthest (if properly sorted, oldest) item in the timeline that is
        // newer than the oldest fetched one, as it's most likely that it delimits the gap.
        // Start the gap *after* that item.
        const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;

        // Then, try to find the furthest (if properly sorted, oldest) item in the timeline that
        // is newer than the most recent fetched one, as it delimits a section comprised of only
        // items older or within `newIds` (or that were deleted from the server, so should be removed
        // anyway).
        // Stop the gap *after* that item.
        const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0) + 1;

        let insertedIds = ImmutableOrderedSet(newIds).withMutations(insertedIds => {
          // It is possible, though unlikely, that the slice we are replacing contains items older
          // than the elements we got from the API. Get them and add them back at the back of the
          // slice.
          const olderIds = oldIds.slice(firstIndex, lastIndex).filter(id => id !== null && compareId(id, newIds.last()) < 0);
          insertedIds.union(olderIds);

          // Make sure we aren't inserting duplicates
          insertedIds.subtract(oldIds.take(firstIndex), oldIds.skip(lastIndex));
        }).toList();

        // Finally, insert a gap marker if the data is marked as partial by the server
        if (isPartial && (firstIndex === 0 || oldIds.get(firstIndex - 1) !== null)) {
          insertedIds = insertedIds.unshift(null);
        }

        return oldIds.take(firstIndex).concat(
          insertedIds,
          oldIds.skip(lastIndex),
        );
      });
    }
  }));
};

const updateTimeline = (state, timeline, status, usePendingItems) => {
  const top = state.getIn([timeline, 'top']);

  if (usePendingItems || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
    if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
      return state;
    }

    return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1));
  }

  const ids        = state.getIn([timeline, 'items'], ImmutableList());
  const includesId = ids.includes(status.get('id'));
  const unread     = state.getIn([timeline, 'unread'], 0);

  if (includesId) {
    return state;
  }

  let newIds = ids;

  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    if (!top) mMap.set('unread', unread + 1);
    if (top && ids.size > 40) newIds = newIds.take(20);
    mMap.set('items', newIds.unshift(status.get('id')));
  }));
};

const deleteStatus = (state, id, references, exclude_account = null) => {
  state.keySeq().forEach(timeline => {
    if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
      const helper = list => list.filterNot(item => item === id);
      state = state.updateIn([timeline, 'items'], helper).updateIn([timeline, 'pendingItems'], helper);
    }
  });

  // Remove reblogs of deleted status
  references.forEach(ref => {
    state = deleteStatus(state, ref, [], exclude_account);
  });

  return state;
};

const clearTimeline = (state, timeline) => {
  return state.set(timeline, initialTimeline);
};

const filterTimelines = (state, relationship, statuses) => {
  let references;

  statuses.forEach(status => {
    if (status.get('account') !== relationship.id) {
      return;
    }

    references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
    state      = deleteStatus(state, status.get('id'), references, relationship.id);
  });

  return state;
};

const filterTimeline = (timeline, state, relationship, statuses) => {
  const helper = list => list.filterNot(statusId => statuses.getIn([statusId, 'account']) === relationship.id);
  return state.updateIn([timeline, 'items'], ImmutableList(), helper).updateIn([timeline, 'pendingItems'], ImmutableList(), helper);
};

const updateTop = (state, timeline, top) => {
  return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
    if (top) mMap.set('unread', mMap.get('pendingItems').size);
    mMap.set('top', top);
  }));
};

export default function timelines(state = initialState, action) {
  switch(action.type) {
  case TIMELINE_LOAD_PENDING:
    return state.update(action.timeline, initialTimeline, map =>
      map.update('items', list => map.get('pendingItems').concat(list.take(40))).set('pendingItems', ImmutableList()).set('unread', 0));
  case TIMELINE_EXPAND_REQUEST:
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
  case TIMELINE_EXPAND_FAIL:
    return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
  case TIMELINE_EXPAND_SUCCESS:
    return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems);
  case TIMELINE_UPDATE:
    return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
  case TIMELINE_DELETE:
    return deleteStatus(state, action.id, action.references, action.reblogOf);
  case TIMELINE_CLEAR:
    return clearTimeline(state, action.timeline);
  case ACCOUNT_BLOCK_SUCCESS:
  case ACCOUNT_MUTE_SUCCESS:
    return filterTimelines(state, action.relationship, action.statuses);
  case ACCOUNT_UNFOLLOW_SUCCESS:
    return filterTimeline('home', state, action.relationship, action.statuses);
  case TIMELINE_SCROLL_TOP:
    return updateTop(state, action.timeline, action.top);
  case TIMELINE_CONNECT:
    return state.update(action.timeline, initialTimeline, map => map.set('online', true));
  case TIMELINE_DISCONNECT:
    return state.update(
      action.timeline,
      initialTimeline,
      map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items),
    );
  case TIMELINE_MARK_AS_PARTIAL:
    return state.update(
      action.timeline,
      initialTimeline,
      map => map.set('isPartial', true).set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('unread', 0),
    );
  default:
    return state;
  }
};
<|endoftext|>


<commit_before>/*global console, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
MAPJS.KineticMediator = function (mapModel, stage) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		};
	stage.add(layer);
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			opacity: 0
		});
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.attrs.shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.attrs.shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, false));
		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
			finalStagePosition = {
				x: stage.attrs.x,
				y: stage.attrs.y
			},
			scale = stage.getScale().x || 1,
			offset = 100;
		console.log(
			'nodeSelectionChanged',
			ideaId,
			'nap',
			node.getAbsolutePosition().x,
			node.getAbsolutePosition().y,
			'node width height',
			node.getWidth(),
			node.getHeight(),
			'stage width height',
			stage.getWidth(),
			stage.getHeight(),
			'node x y',
			node.attrs.x,
			node.attrs.y,
			'stage x y',
			stage.attrs.x,
			stage.attrs.y
		);
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
			finalStagePosition.x = stage.getWidth() - node.attrs.x - node.getWidth() * scale - offset;
		} else if (node.getAbsolutePosition().x < offset) {
			finalStagePosition.x = -node.attrs.x + offset;
		}
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
			finalStagePosition.y = stage.getHeight() - node.attrs.y - node.getHeight() * scale - offset;
		} else if (node.getAbsolutePosition().y < offset) {
			finalStagePosition.y = -node.attrs.y + offset;
		}
		stage.transitionTo({
			x: finalStagePosition.x,
			y: finalStagePosition.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
		node.setIsSelected(isSelected);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (isScaleUp) {
		var scale = stage.getScale();
		scale.y = scale.x = (scale.x || 1) * (isScaleUp ? 1.25 : 0.8);
		stage.setScale(scale);
		stage.draw();
	});
	(function () {
		var keyboardEventHandlers = {
			13: mapModel.addSiblingIdea,
			8: mapModel.removeSubIdea,
			9: mapModel.addSubIdea,
			37: mapModel.selectNodeLeft,
			38: mapModel.selectNodeUp,
			39: mapModel.selectNodeRight,
			40: mapModel.selectNodeDown,
			46: mapModel.removeSubIdea,
			32: mapModel.editNode
		},
			onKeydown = function (evt) {
				var eventHandler = keyboardEventHandlers[evt.which];
				if (eventHandler) {
					eventHandler();
					evt.preventDefault();
				}
			};
		jQuery(document).keydown(onKeydown);
		mapModel.addEventListener('inputEnabledChanged', function (isInputEnabled) {
			jQuery(document)[isInputEnabled ? 'bind' : 'unbind']('keydown', onKeydown);
		});
	}());
};
MAPJS.KineticMediator.dimensionProvider = function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
};
<commit_msg>fixing show selected node - incomplete<commit_after>/*global console, document, jQuery, Kinetic*/
var MAPJS = MAPJS || {};
MAPJS.KineticMediator = function (mapModel, stage) {
	'use strict';
	var layer = new Kinetic.Layer(),
		nodeByIdeaId = {},
		connectorByFromIdeaId_ToIdeaId = {},
		connectorKey = function (fromIdeaId, toIdeaId) {
			return fromIdeaId + '_' + toIdeaId;
		};
	stage.add(layer);
	mapModel.addEventListener('nodeCreated', function (n) {
		var node = new Kinetic.Idea({
			level: n.level,
			x: n.x,
			y: n.y,
			text: n.title,
			opacity: 0
		});
		/* in kinetic 4.3 cannot use click because click if fired on dragend */
		node.on('click tap', mapModel.selectNode.bind(mapModel, n.id));
		node.on('dragstart', function () {
			node.moveToTop();
			node.attrs.shadow.offset = {
				x: 8,
				y: 8
			};
		});
		node.on('dragmove', function () {
			mapModel.nodeDragMove(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on('dragend', function () {
			node.attrs.shadow.offset = {
				x: 4,
				y: 4
			};
			mapModel.nodeDragEnd(
				n.id,
				node.attrs.x,
				node.attrs.y
			);
		});
		node.on(':textChanged', function (event) {
			mapModel.updateTitle(n.id, event.text);
		});
		node.on(':nodeEditRequested', mapModel.editNode.bind(mapModel, false));
		mapModel.addEventListener('nodeEditRequested:' + n.id, node.editNode);
		nodeByIdeaId[n.id] = node;
		layer.add(node);
		node.transitionToAndDontStopCurrentTransitions({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('nodeSelectionChanged', function (ideaId, isSelected) {
		var node = nodeByIdeaId[ideaId],
		stagePosition = {
			x: stage.attrs.x,
			y: stage.attrs.y
		},

			finalStagePosition = {
				x: stage.attrs.x,
				y: stage.attrs.y
			},
			scale = stage.getScale().x || 1,
			offset = 20;
			console.log ('-->finalStagePosition',finalStagePosition.x,finalStagePosition.y)

		console.log(
			'nodeSelectionChanged',
			ideaId,
			'nap',
			node.getAbsolutePosition().x,
			node.getAbsolutePosition().y,
			'node width height',
			node.getWidth(),
			node.getHeight(),
			'stage width height',
			stage.getWidth(),
			stage.getHeight(),
			'node x y',
			node.attrs.x,
			node.attrs.y,
			'stage x y',
			stage.attrs.x,
			stage.attrs.y
		);
		var movex = 0;
		if (node.getAbsolutePosition().x + node.getWidth() * scale + offset > stage.getWidth()) {
		  var movex = stage.getWidth() - (node.getAbsolutePosition().x + node.getWidth() * scale + offset);
			finalStagePosition.x = stagePosition.x + movex;
		} else if (node.getAbsolutePosition().x < offset) {
		  var movex  = offset - node.getAbsolutePosition().x;
		  finalStagePosition.x = stagePosition.x + movex;
		}
		finalStagePosition.x = stagePosition.x + movex;
		if (node.getAbsolutePosition().y + node.getHeight() * scale + offset > stage.getHeight()) {
		  var movey = stage.getHeight() - (node.getAbsolutePosition().y + node.getHeight() * scale + offset);
			finalStagePosition.y = stagePosition.y + movey;
		} else if (node.getAbsolutePosition().y < offset) {
		  var movey = offset - node.getAbsolutePosition().y;
			finalStagePosition.y = stagePosition.y + movey;
		}
		console.log ('<--finalStagePosition',finalStagePosition.x,finalStagePosition.y)

		stage.transitionTo({
			x: finalStagePosition.x,
			y: finalStagePosition.y,
			duration: 0.4,
			easing: 'ease-in-out'
		});
		node.setIsSelected(isSelected);
	});
	mapModel.addEventListener('nodeDroppableChanged', function (ideaId, isDroppable) {
		var node = nodeByIdeaId[ideaId];
		node.setIsDroppable(isDroppable);
	});
	mapModel.addEventListener('nodeRemoved', function (n) {
		var node = nodeByIdeaId[n.id];
		delete nodeByIdeaId[n.id];
		node.transitionTo({
			opacity: 0.25,
			duration: 0.4,
			callback: node.remove.bind(node)
		});
		node.off('click dblclick tap dragstart dragmove dragend mouseover mouseout :textChanged :nodeEditRequested');
		mapModel.removeEventListener('nodeEditRequested:' + n.id, node.editNode);
	});
	mapModel.addEventListener('nodeMoved', function (n, reason) {
		var node = nodeByIdeaId[n.id];
		node.transitionTo({
			x: n.x,
			y: n.y,
			duration: 0.4,
			easing: reason === 'failed' ? 'bounce-ease-out' : 'ease-in-out'
		});
	});
	mapModel.addEventListener('nodeTitleChanged', function (n) {
		var node = nodeByIdeaId[n.id];
		node.setText(n.title);
		layer.draw();
	});
	mapModel.addEventListener('connectorCreated', function (n) {
		var connector = new Kinetic.Connector({
			shapeFrom: nodeByIdeaId[n.from],
			shapeTo: nodeByIdeaId[n.to],
			stroke: '#888',
			strokeWidth: 1,
			opacity: 0
		});
		connector.opacity = 0;
		connectorByFromIdeaId_ToIdeaId[connectorKey(n.from, n.to)] = connector;
		layer.add(connector);
		connector.moveToBottom();
		connector.transitionTo({
			opacity: 1,
			duration: 0.4
		});
	});
	mapModel.addEventListener('connectorRemoved', function (n) {
		var key = connectorKey(n.from, n.to),
			connector = connectorByFromIdeaId_ToIdeaId[key];
		delete connectorByFromIdeaId_ToIdeaId[key];
		connector.transitionTo({
			opacity: 0,
			duration: 0.1,
			callback: connector.remove.bind(connector)
		});
	});
	mapModel.addEventListener('mapScaleChanged', function (isScaleUp) {
		var scale = stage.getScale();
		scale.y = scale.x = (scale.x || 1) * (isScaleUp ? 1.25 : 0.8);
		stage.setScale(scale);
		stage.draw();
	});
	(function () {
		var keyboardEventHandlers = {
			13: mapModel.addSiblingIdea,
			8: mapModel.removeSubIdea,
			9: mapModel.addSubIdea,
			37: mapModel.selectNodeLeft,
			38: mapModel.selectNodeUp,
			39: mapModel.selectNodeRight,
			40: mapModel.selectNodeDown,
			46: mapModel.removeSubIdea,
			32: mapModel.editNode
		},
			onKeydown = function (evt) {
				var eventHandler = keyboardEventHandlers[evt.which];
				if (eventHandler) {
					eventHandler();
					evt.preventDefault();
				}
			};
		jQuery(document).keydown(onKeydown);
		mapModel.addEventListener('inputEnabledChanged', function (isInputEnabled) {
			jQuery(document)[isInputEnabled ? 'bind' : 'unbind']('keydown', onKeydown);
		});
	}());
};
MAPJS.KineticMediator.dimensionProvider = function (title) {
	'use strict';
	var text = new Kinetic.Idea({
		text: title
	});
	return {
		width: text.getWidth(),
		height: text.getHeight()
	};
};
<|endoftext|>


<commit_before>import _ from 'underscore'
import $ from 'jquery'
import moment from 'moment';
import {CompositeView, ItemView} from 'backbone.marionette'
import {Model} from 'backbone'
import {localStorage} from '../../../utils'
import app from '../../../application'

class PlaylistItem extends ItemView {
    get className() {
        return 'playlist-item js-playlist-item';
    }

    get template() {
        return require('../templates/playlistItem.ejs');
    }

    ui() {
        return {
            link: '.js-link'
        }
    }

    bindings() {
        return {
            ':el': {
                classes: {
                    'watched': '_watched'
                }
            }
        }
    }

    triggers() {
        return {
            'click @ui.link': 'link:clicked'
        }
    }

    initialize() {
        const videoId = this.model.get('videoId');
        const watched = !!localStorage.get(`${videoId}.info`, 'watched');

        if (watched) {
            this.model.set('_watched', true);
        }
    }

    onRender() {
        this.$el.attr('data-videoid', this.model.get('videoId'));

        this.stickit();
    }
}

let autoplay = false;

export default class PlaylistItems extends CompositeView {

    constructor(options = {}) {
        _.defaults(options, {
            model: new Model({
                _search: '',
                _searchDate: null,
                videoId: null
            })
        });

        super(options);
    }

    ui() {
        return {
            search: '.js-search',
            datepicker: '.js-datepicker'
        }
    }

    get childView() {
        return PlaylistItem;
    }

    get childViewContainer() {
        return '.js-playlist-items'
    }

    get template() {
        return require('../templates/playlistItems.ejs');
    }

    set videoId(val) {
        this.model.set('videoId', val);
    }

    /**
     * @returns {{search: String}}
     */
    get playlistFilter() {
        return {
            search: this.model.get('_search'),
            date: this.model.get('_searchDate')
        }
    }

    childEvents() {
        return {
            'link:clicked': '_onClickLink'
        }
    }

    modelEvents() {
        return {
            'change:videoId': '_highlightVideo _routeToVideo',

            'change:_search change:_searchDate': _.debounce(() => {
                this._searchCollection();
                this._highlightVideo();
            }, 50)
        }
    }

    bindings() {
        return {
            '.js-video-container': {
                observe: 'videoId',
                update: _.debounce(this._videoPlay, 500)
            },

            '@ui.search': '_search',

            '@ui.datepicker': {
                observe: '_searchDate',
                onSet: (val) => {
                    return val ? moment(val, 'DD.MM.YYYY') : null;
                }
            }
        }
    }

    initialize() {
        this._playerInterval = 0;
    }

    onRender() {
        this.ui.datepicker.datepicker({
            format: 'dd.mm.yyyy',
            autoclose: true,
            weekStart: 1
        });

        this.stickit();
    }

    _searchCollection() {
        const filter = this.playlistFilter;

        this.collection.search(filter);
    }

    _onClickLink(playlistItem) {
        this.videoId = playlistItem.ui.link.data('videoid');
    }

    _highlightVideo() {
        this.$childViewContainer.find('.js-playlist-item').removeClass('active');

        const $videoId = this.$childViewContainer.find('[data-videoid="' + this.model.get('videoId') + '"]');

        $videoId.addClass('active');

        // Scroll
        this.$childViewContainer
            .animate({
                scrollTop: ($videoId.index() - 1) * 65
            }, 250);
    }

    _routeToVideo() {
        const currentPlaylistItem = this.collection.getCurrentPlaylistItem(this.model.get('videoId'));

        app.navigate(`playlists/playlist/${currentPlaylistItem.get('playlistId')}/video/${currentPlaylistItem.get('videoId')}`);
    }

    _videoPlay() {
        clearInterval(this._playerInterval);

        const videoId = this.model.get('videoId');

        if (videoId) {
            $('#yt-video-container').replaceWith('

');

            this._player = new YT.Player('yt-video-container', {
                height: '390',
                width: '640',
                videoId: videoId,
                events: {
                    'onReady': this._onVideoReady.bind(this),
                    'onStateChange': this._onVideoStateChange.bind(this)
                }
            });
        }
    }

    _videoBeforePlay() {
        const videoId = this.model.get('videoId');

        // currentTime
        const videoInfo = localStorage.get(`${videoId}.info`);

        if (videoInfo && videoInfo.currentTime) {
            this._player.seekTo(videoInfo.currentTime);
        }
    }

    _videoPlaying() {
        const videoId = this.model.get('videoId');

        // Store player-status
        clearInterval(this._playerInterval);

        const updateCurrentTime = () => {
            const currentTime = Math.round(this._player.getCurrentTime());

            localStorage.update(`${videoId}.info`, { currentTime });
        };

        updateCurrentTime();
        this._playerInterval = setInterval(updateCurrentTime, 8000);
    }

    _videoEnded() {
        const videoId = this.model.get('videoId');

        // Mark as watched
        this.collection.getCurrentPlaylistItem(videoId).set('_watched', true);
        localStorage.update(`${videoId}.info`, { watched: true, currentTime: 0 });

        // Play next
        const nextPlaylistItem = this.collection.getNextPlaylistItem(videoId);

        if (nextPlaylistItem) {
            const nextVideoId = nextPlaylistItem.get('videoId');

            // Reset currentTime
            localStorage.update(`${nextVideoId}.info`, { currentTime: 0 });

            // Set new videoId
            this.videoId = nextVideoId;

            // Start video automatically
            autoplay = true;
        }
    }

    _onVideoReady(e) {
        if (_.isNull(e.data)) {
            // autoplay
            if (autoplay) {
                this._player.playVideo();

                autoplay = false;
            }
        }
    }

    _onVideoStateChange(e) {
        const videoId = this.model.get('videoId');

        switch (e.data) {
            case YT.PlayerState.UNSTARTED:
                this._videoBeforePlay();
                break;
            case YT.PlayerState.PLAYING:
                this._videoPlaying();
                break;
            case YT.PlayerState.ENDED:
                this._videoEnded();
                break;
        }
    }

}<commit_msg>If player exists, load new video instead replacing it<commit_after>import _ from 'underscore'
import $ from 'jquery'
import moment from 'moment';
import {CompositeView, ItemView} from 'backbone.marionette'
import {Model} from 'backbone'
import {localStorage} from '../../../utils'
import app from '../../../application'

class PlaylistItem extends ItemView {
    get className() {
        return 'playlist-item js-playlist-item';
    }

    get template() {
        return require('../templates/playlistItem.ejs');
    }

    ui() {
        return {
            link: '.js-link'
        }
    }

    bindings() {
        return {
            ':el': {
                classes: {
                    'watched': '_watched'
                }
            }
        }
    }

    triggers() {
        return {
            'click @ui.link': 'link:clicked'
        }
    }

    initialize() {
        const videoId = this.model.get('videoId');
        const watched = !!localStorage.get(`${videoId}.info`, 'watched');

        if (watched) {
            this.model.set('_watched', true);
        }
    }

    onRender() {
        this.$el.attr('data-videoid', this.model.get('videoId'));

        this.stickit();
    }
}

let autoplay = false;

export default class PlaylistItems extends CompositeView {

    constructor(options = {}) {
        _.defaults(options, {
            model: new Model({
                _search: '',
                _searchDate: null,
                videoId: null
            })
        });

        super(options);
    }

    ui() {
        return {
            search: '.js-search',
            datepicker: '.js-datepicker'
        }
    }

    get childView() {
        return PlaylistItem;
    }

    get childViewContainer() {
        return '.js-playlist-items'
    }

    get template() {
        return require('../templates/playlistItems.ejs');
    }

    set videoId(val) {
        this.model.set('videoId', val);
    }

    /**
     * @returns {{search: String}}
     */
    get playlistFilter() {
        return {
            search: this.model.get('_search'),
            date: this.model.get('_searchDate')
        }
    }

    childEvents() {
        return {
            'link:clicked': '_onClickLink'
        }
    }

    modelEvents() {
        return {
            'change:videoId': '_highlightVideo _routeToVideo',

            'change:_search change:_searchDate': _.debounce(() => {
                this._searchCollection();
                this._highlightVideo();
            }, 50)
        }
    }

    bindings() {
        return {
            '.js-video-container': {
                observe: 'videoId',
                update: _.debounce(this._videoPlay, 500)
            },

            '@ui.search': '_search',

            '@ui.datepicker': {
                observe: '_searchDate',
                onSet: (val) => {
                    return val ? moment(val, 'DD.MM.YYYY') : null;
                }
            }
        }
    }

    initialize() {
        this._playerInterval = 0;
    }

    onRender() {
        this.ui.datepicker.datepicker({
            format: 'dd.mm.yyyy',
            autoclose: true,
            weekStart: 1
        });

        this.stickit();
    }

    _searchCollection() {
        const filter = this.playlistFilter;

        this.collection.search(filter);
    }

    _onClickLink(playlistItem) {
        this.videoId = playlistItem.ui.link.data('videoid');
    }

    _highlightVideo() {
        this.$childViewContainer.find('.js-playlist-item').removeClass('active');

        const $videoId = this.$childViewContainer.find('[data-videoid="' + this.model.get('videoId') + '"]');

        $videoId.addClass('active');

        // Scroll
        this.$childViewContainer
            .animate({
                scrollTop: ($videoId.index() - 1) * 65
            }, 250);
    }

    _routeToVideo() {
        const currentPlaylistItem = this.collection.getCurrentPlaylistItem(this.model.get('videoId'));

        app.navigate(`playlists/playlist/${currentPlaylistItem.get('playlistId')}/video/${currentPlaylistItem.get('videoId')}`);
    }

    _videoPlay() {
        clearInterval(this._playerInterval);

        const videoId = this.model.get('videoId');

        if (videoId) {
            if(this._player) {
                this._player.loadVideoById(videoId, 0);
            } else {
                $('#yt-video-container').replaceWith('

');

                this._player = new YT.Player('yt-video-container', {
                    height: '390',
                    width: '640',
                    videoId: videoId,
                    events: {
                        'onReady': this._onVideoReady.bind(this),
                        'onStateChange': this._onVideoStateChange.bind(this)
                    }
                });
            }
        }
    }

    _videoBeforePlay() {
        const videoId = this.model.get('videoId');

        // currentTime
        const videoInfo = localStorage.get(`${videoId}.info`);

        if (videoInfo && videoInfo.currentTime) {
            this._player.seekTo(videoInfo.currentTime);
        }
    }

    _videoPlaying() {
        const videoId = this.model.get('videoId');

        // Store player-status
        clearInterval(this._playerInterval);

        const updateCurrentTime = () => {
            const currentTime = Math.round(this._player.getCurrentTime());

            localStorage.update(`${videoId}.info`, { currentTime });
        };

        updateCurrentTime();
        this._playerInterval = setInterval(updateCurrentTime, 8000);
    }

    _videoEnded() {
        const videoId = this.model.get('videoId');

        // Mark as watched
        this.collection.getCurrentPlaylistItem(videoId).set('_watched', true);
        localStorage.update(`${videoId}.info`, { watched: true, currentTime: 0 });

        // Play next
        const nextPlaylistItem = this.collection.getNextPlaylistItem(videoId);

        if (nextPlaylistItem) {
            const nextVideoId = nextPlaylistItem.get('videoId');

            // Reset currentTime
            localStorage.update(`${nextVideoId}.info`, { currentTime: 0 });

            // Set new videoId
            this.videoId = nextVideoId;

            // Start video automatically
            autoplay = true;
        }
    }

    _onVideoReady(e) {
        if (_.isNull(e.data)) {
            // autoplay
            if (autoplay) {
                this._player.playVideo();

                autoplay = false;
            }
        }
    }

    _onVideoStateChange(e) {
        const videoId = this.model.get('videoId');

        switch (e.data) {
            case YT.PlayerState.UNSTARTED:
                this._videoBeforePlay();
                break;
            case YT.PlayerState.PLAYING:
                this._videoPlaying();
                break;
            case YT.PlayerState.ENDED:
                this._videoEnded();
                break;
        }
    }

}<|endoftext|>