<commit_before>var _ = require('underscore');
var Backbone = require('backbone');
var InputColor = require('builder/components/input-color/input-color');
var rampList = require('builder/components/input-color/input-quantitative-ramps/ramps');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var FactoryModals = require('../../../../factories/modals');

describe('components/form-components/editors/fill/input-color', function () {
  var svgResponse = {
    status: 200,
    contentType: 'image/svg+xml',
    responseText: '<svg xmlns:a="genius.com" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"><path></path></svg>'
  };
  var imgUrl = 'http://www.image.com/image.svg';
  var fixed = '#c0ffee';

  beforeEach(function () {
    this.configModel = new ConfigModel({
      base_url: '/u/pepe'
    });

    this.userModel = new UserModel({}, {
      configModel: this.configModel
    });

    this.modals = FactoryModals.createModalService();

    this.model = new Backbone.Model({
      bins: 5,
      range: ['#FFF', '#FABADA', '#00FF00', '#000', '#999999'],
      attribute: 'column1',
      quantification: 'jenks',
      opacity: 0.5
    });
    this.view = new InputColor(({
      model: this.model,
      userModel: this.userModel,
      configModel: this.configModel,
      query: 'SELECT * from table',
      columns: [
        { label: 'column1', type: 'number' },
        { label: 'column2', type: 'number' },
        { label: 'column3', type: 'number' }
      ]
    }));
    this.view.render();
  });

  afterEach(function () {
    this.view.remove();
  });

  it('should render properly', function () {
    expect(_.size(this.view._subviews)).toBe(1);
  });

  it('should get selected', function () {
    this.model.set('selected', true);
    expect(this.view.$el.hasClass('is-active')).toBeTruthy();
  });

  it('should create a content view', function () {
    expect(this.view.model.get('createContentView')).toBeDefined();
  });

  it('should trigger a click event when clicked', function () {
    var clickEvent = false;
    this.view.bind('click', function () {
      clickEvent = true;
    });
    this.view.$el.click();
    expect(clickEvent).toBeTruthy();
  });

  describe('range', function () {
    beforeEach(function () {
      this.model = new Backbone.Model({
        bins: 5,
        range: ['#FFFFFF', '#FABADA', '#00FF00', '#000000', '#999999'],
        attribute: 'column1',
        quantification: 'Jenks',
        opacity: 0.50
      });
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ]
      }));
      this.view.render();
    });

    it('should render properly', function () {
      expect(this.view.$('.ColorBar').length).toBe(1);
    });

    it('should update when the value has changed', function () {
      this.model.set('range', ['#FFFFFF', '#FFFF00', '#0000FF']);
      expect(this.view.$('.ColorBar').length).toBe(1);
      expect(this.view.$('.ColorBar').attr('style')).toBeDefined();
      expect(this.view._getValue()).toEqual(['rgba(255, 255, 255, 0.5)', 'rgba(255, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)']);
    });

    it('should set opacity for each color', function () {
      this.model.set({
        range: ['#FABADA', '#00FFAA', '#0000FF'],
        opacity: 0.99
      });
      expect(this.view.$('.ColorBar').length).toBe(1);
      expect(this.view.$('.ColorBar').attr('style')).toBeDefined();
      expect(this.view._getValue()).toEqual(['rgba(250, 186, 218, 0.99)', 'rgba(0, 255, 170, 0.99)', 'rgba(0, 0, 255, 0.99)']);
    });
  });

  describe('image', function () {
    describe('without image enabled', function () {
      it('should not render the image', function () {
        this.view = new InputColor(({
          model: new Backbone.Model({
            opacity: 0.5,
            fixed: fixed,
            attribute: 'column1'
          }),
          userModel: {
            featureEnabled: function () {
              return true;
            }
          },
          modals: this.modals,
          configModel: {},
          query: 'SELECT * from table',
          columns: [
            { label: 'column1', type: 'string' }
          ]
        }));

        this.view.render();

        expect(this.view.$('.Editor-fillImage').length).toBe(0);
      });
    });

    describe('with image enabled', function () {
      describe('without image', function () {
        it('should render image link if category images are present', function () {
          this.view = new InputColor(({
            model: new Backbone.Model({
              opacity: 0.5,
              fixed: fixed,
              images: [imgUrl, imgUrl, imgUrl],
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          this.view.render();

          expect(this.view.$('.Editor-fillImage').html()).toContain('form-components.editors.fill.input-color.img');
        });

        it('should not render image link if category images are not present', function () {
          this.view = new InputColor(({
            model: new Backbone.Model({
              opacity: 0.5,
              fixed: fixed,
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          this.view.render();

          expect(this.view.$('.Editor-fillImage').html()).not.toContain('Img');
        });
      });

      describe('with image', function () {
        var fixture;

        beforeEach(function () {
          jasmine.Ajax.install();
          jasmine.Ajax.stubRequest(new RegExp('^http(s)?.*/image.svg'))
            .andReturn(svgResponse);

          this.view = new InputColor(({
            model: new Backbone.Model({
              image: imgUrl,
              opacity: 0.5,
              fixed: fixed,
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          // For getting right values from the SVG element, it needs to be attached to the DOM
          fixture = document.createElement('div');
          fixture.id = 'fixture';
          document.getElementsByTagName('body')[0].appendChild(fixture);
          fixture.appendChild(this.view.$el[0]);

          this.view.render();
        });

        afterEach(function () {
          fixture.parentNode.removeChild(fixture);
          jasmine.Ajax.uninstall();
        });

        it('should render the image', function () {
          expect(this.view.$('.Editor-fillImage').length).toBe(1);
        });

        it('SVG should have been loaded', function () {
          var image = this.view.$('.js-image');

          expect(image[0].tagName.toUpperCase()).toBe('SVG');
          expect(image.attr('xmlns:a')).toBeUndefined();
          expect(image.attr('class')).toContain('Editor-fillImageAsset');
          expect(image.attr('class')).toContain('js-image');
          var cssFill = image.css('fill').toLowerCase();
          var pathFill = image.find('path').css('fill').toLowerCase();
          expect(cssFill === 'rgb(192, 255, 238)' || cssFill === '#c0ffee').toBe(true);
          expect(pathFill === 'rgb(192, 255, 238)' || pathFill === '#c0ffee').toBe(true);
        });

        it('._updateImageColor should change fill attribute', function () {
          // Trigger _updateImageColor via model change
          this.view.model.set('fixed', '#0FF1CE'); // rgb(15, 241, 206)

          var image = this.view.$('.js-image');
          var cssFill = image.css('fill').toLowerCase();
          expect(cssFill === 'rgb(15, 241, 206)' || cssFill === '#0ff1ce').toBe(true);
        });
      });
    });
  });

  describe('migrate old range', function () {
    beforeEach(function () {
      this.model = new Backbone.Model({
        bins: 5,
        range: 'inverted_green',
        attribute: 'column1',
        quantification: 'jenks',
        opacity: 0.5
      });
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ]
      }));
      this.view.render();
    });

    it('should migrate the range', function () {
      expect(this.model.get('range')).toBe(rampList['inverted_green'][5]);
    });
  });

  describe('with help', function () {
    beforeEach(function () {
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ],
        editorAttrs: {
          help: 'help'
        }
      }));
    });

    describe('.render', function () {
      it('should render properly', function () {
        this.view.render();

        expect(_.size(this.view._subviews)).toBe(2);
        expect(this.view.$('.js-help').attr('data-tooltip')).toContain('help');
      });
    });
  });

  it('should not have any leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });
});
<commit_msg>Update unit tests<commit_after>var _ = require('underscore');
var Backbone = require('backbone');
var InputColor = require('builder/components/input-color/input-color');
var rampList = require('builder/components/input-color/input-quantitative-ramps/ramps');
var ConfigModel = require('builder/data/config-model');
var UserModel = require('builder/data/user-model');
var FactoryModals = require('../../../../factories/modals');

describe('components/form-components/editors/fill/input-color', function () {
  var svgResponse = {
    status: 200,
    contentType: 'image/svg+xml',
    responseText: '<svg xmlns:a="genius.com" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"><path></path></svg>'
  };
  var imgUrl = 'http://www.image.com/image.svg';
  var fixed = '#c0ffee';

  beforeEach(function () {
    this.configModel = new ConfigModel({
      base_url: '/u/pepe'
    });

    this.userModel = new UserModel({}, {
      configModel: this.configModel
    });

    this.modals = FactoryModals.createModalService();

    this.model = new Backbone.Model({
      bins: 5,
      range: ['#FFF', '#FABADA', '#00FF00', '#000', '#999999'],
      attribute: 'column1',
      quantification: 'jenks',
      opacity: 0.5
    });
    this.view = new InputColor(({
      model: this.model,
      userModel: this.userModel,
      configModel: this.configModel,
      query: 'SELECT * from table',
      columns: [
        { label: 'column1', type: 'number' },
        { label: 'column2', type: 'number' },
        { label: 'column3', type: 'number' }
      ]
    }));
    this.view.render();
  });

  afterEach(function () {
    this.view.remove();
  });

  it('should render properly', function () {
    expect(_.size(this.view._subviews)).toBe(1);
  });

  it('should get selected', function () {
    this.model.set('selected', true);
    expect(this.view.$el.hasClass('is-active')).toBeTruthy();
  });

  it('should create a content view', function () {
    expect(this.view.model.get('createContentView')).toBeDefined();
  });

  it('should trigger a click event when clicked', function () {
    var clickEvent = false;
    this.view.bind('click', function () {
      clickEvent = true;
    });
    this.view.$el.click();
    expect(clickEvent).toBeTruthy();
  });

  describe('range', function () {
    beforeEach(function () {
      this.model = new Backbone.Model({
        bins: 5,
        range: ['#FFFFFF', '#FABADA', '#00FF00', '#000000', '#999999'],
        attribute: 'column1',
        quantification: 'Jenks',
        opacity: 0.50
      });
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ]
      }));
      this.view.render();
    });

    it('should render properly', function () {
      expect(this.view.$('.ColorBar').length).toBe(1);
    });

    it('should update when the value has changed', function () {
      this.model.set('range', ['#FFFFFF', '#FFFF00', '#0000FF']);
      expect(this.view.$('.ColorBar').length).toBe(1);
      expect(this.view.$('.ColorBar').attr('style')).toBeDefined();
      expect(this.view._getValue()).toEqual(['rgba(255, 255, 255, 0.5)', 'rgba(255, 255, 0, 0.5)', 'rgba(0, 0, 255, 0.5)']);
    });

    it('should set opacity for each color', function () {
      this.model.set({
        range: ['#FABADA', '#00FFAA', '#0000FF'],
        opacity: 0.99
      });
      expect(this.view.$('.ColorBar').length).toBe(1);
      expect(this.view.$('.ColorBar').attr('style')).toBeDefined();
      expect(this.view._getValue()).toEqual(['rgba(250, 186, 218, 0.99)', 'rgba(0, 255, 170, 0.99)', 'rgba(0, 0, 255, 0.99)']);
    });
  });

  describe('image', function () {
    describe('without image enabled', function () {
      it('should not render the image', function () {
        this.view = new InputColor(({
          model: new Backbone.Model({
            opacity: 0.5,
            fixed: fixed,
            attribute: 'column1'
          }),
          userModel: {
            featureEnabled: function () {
              return true;
            }
          },
          modals: this.modals,
          configModel: {},
          query: 'SELECT * from table',
          columns: [
            { label: 'column1', type: 'string' }
          ]
        }));

        this.view.render();

        expect(this.view.$('.Editor-fillImage').length).toBe(0);
      });
    });

    describe('with image enabled', function () {
      describe('without image', function () {
        it('should render image link if category images are present', function () {
          this.view = new InputColor(({
            model: new Backbone.Model({
              opacity: 0.5,
              fixed: fixed,
              images: [imgUrl, imgUrl, imgUrl],
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          this.view.render();

          expect(this.view.$('.Editor-fillImage').html()).toContain('form-components.editors.fill.input-color.img');
        });

        it('should not render image link if category images are not present', function () {
          this.view = new InputColor(({
            model: new Backbone.Model({
              opacity: 0.5,
              fixed: fixed,
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          this.view.render();

          expect(this.view.$('.Editor-fillImage').html()).not.toContain('Img');
        });
      });

      describe('with image', function () {
        var fixture;

        beforeEach(function () {
          jasmine.Ajax.install();
          jasmine.Ajax.stubRequest(new RegExp('^http(s)?.*/image.svg'))
            .andReturn(svgResponse);

          this.view = new InputColor(({
            model: new Backbone.Model({
              image: imgUrl,
              opacity: 0.5,
              fixed: fixed,
              attribute: 'column1'
            }),
            userModel: {
              featureEnabled: function () {
                return true;
              }
            },
            modals: this.modals,
            configModel: {},
            editorAttrs: {
              imageEnabled: true
            },
            query: 'SELECT * from table',
            columns: [
              { label: 'column1', type: 'string' }
            ]
          }));

          // For getting right values from the SVG element, it needs to be attached to the DOM
          fixture = document.createElement('div');
          fixture.id = 'fixture';
          document.getElementsByTagName('body')[0].appendChild(fixture);
          fixture.appendChild(this.view.$el[0]);

          this.view.render();
        });

        afterEach(function () {
          fixture.parentNode.removeChild(fixture);
          jasmine.Ajax.uninstall();
        });

        it('should render the image', function () {
          expect(this.view.$('.Editor-fillImage').length).toBe(1);
        });

        it('SVG should have been loaded', function () {
          var image = this.view.$('.js-image');

          expect(image[0].tagName.toUpperCase()).toBe('SVG');
          expect(image.attr('xmlns:a')).toBeUndefined();
          expect(image.attr('class')).toContain('Editor-fillImageAsset');
          expect(image.attr('class')).toContain('js-image');
          var cssFill = image.css('fill').toLowerCase();
          var pathFill = image.find('path').css('fill').toLowerCase();
          expect(cssFill === 'rgb(192, 255, 238)' || cssFill === '#c0ffee').toBe(true);
          expect(pathFill === 'rgb(192, 255, 238)' || pathFill === '#c0ffee').toBe(true);
        });

        it('should change fill attribute when color changes', function () {
          // Trigger _updateImageColor via model change
          this.view.model.set('fixed', '#0FF1CE'); // rgb(15, 241, 206)

          var image = this.view.$('.js-image');
          var cssFill = image.css('fill').toLowerCase();
          expect(cssFill).toBe('rgba(15, 241, 206, 0.5)'); // Default opacity 0.5
        });

        it('should change fill attribute when opacity changes', function () {
          // Trigger _updateImageColor via model change
          this.view.model.set('opacity', 0.1);

          var image = this.view.$('.js-image');
          var cssFill = image.css('fill').toLowerCase();
          expect(cssFill).toBe('rgba(192, 255, 238, 0.1)'); // Default color rgb(192, 255, 238)
        });
      });
    });
  });

  describe('migrate old range', function () {
    beforeEach(function () {
      this.model = new Backbone.Model({
        bins: 5,
        range: 'inverted_green',
        attribute: 'column1',
        quantification: 'jenks',
        opacity: 0.5
      });
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ]
      }));
      this.view.render();
    });

    it('should migrate the range', function () {
      expect(this.model.get('range')).toBe(rampList['inverted_green'][5]);
    });
  });

  describe('with help', function () {
    beforeEach(function () {
      this.view = new InputColor(({
        model: this.model,
        userModel: this.userModel,
        configModel: this.configModel,
        query: 'SELECT * from table',
        columns: [
          { label: 'column1', type: 'number' },
          { label: 'column2', type: 'number' },
          { label: 'column3', type: 'number' }
        ],
        editorAttrs: {
          help: 'help'
        }
      }));
    });

    describe('.render', function () {
      it('should render properly', function () {
        this.view.render();

        expect(_.size(this.view._subviews)).toBe(2);
        expect(this.view.$('.js-help').attr('data-tooltip')).toContain('help');
      });
    });
  });

  it('should not have any leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });
});
<|endoftext|>


<commit_before>//Dont change it
requirejs(['ext_editor_1', 'jquery_190', 'raphael_210', 'snap.svg_030'],
    function (ext, $, Raphael, Snap) {

        var cur_slide = {};

        ext.set_start_game(function (this_e) {
        });

        ext.set_process_in(function (this_e, data) {
            cur_slide = {};
            cur_slide["in"] = data[0];
            this_e.addAnimationSlide(cur_slide);
        });

        ext.set_process_out(function (this_e, data) {
            cur_slide["out"] = data[0];
        });

        ext.set_process_ext(function (this_e, data) {
            cur_slide.ext = data;
        });

        ext.set_process_err(function (this_e, data) {
            cur_slide['error'] = data[0];
            this_e.addAnimationSlide(cur_slide);
            cur_slide = {};
        });

        ext.set_animate_success_slide(function (this_e, options) {
            var $h = $(this_e.setHtmlSlide('

'));
            this_e.setAnimationHeight(115);
        });

        ext.set_animate_slide(function (this_e, data, options) {
            var $content = $(this_e.setHtmlSlide(ext.get_template('animation'))).find('.animation-content');
            if (!data) {
                console.log("data is undefined");
                return false;
            }

            //YOUR FUNCTION NAME
            var fname = 'draw';

            var checkioInput = data.in;
            var checkioInputStr = fname + '(' +
                JSON.stringify(checkioInput).replace(/\[/g, "(").replace(/]/g, ")").replace("((", "{(").replace("))", ")}")
                + ')';

            var failError = function (dError) {
                $content.find('.call').html(checkioInputStr);
                $content.find('.output').html(dError.replace(/\n/g, ","));

                $content.find('.output').addClass('error');
                $content.find('.call').addClass('error');
                $content.find('.answer').remove();
                $content.find('.explanation').remove();
                this_e.setAnimationHeight($content.height() + 60);
            };

            if (data.error) {
                failError(data.error);
                return false;
            }

            if (data.ext && data.ext.inspector_fail) {
                failError(data.ext.inspector_result_addon);
                return false;
            }

            $content.find('.call').html(checkioInputStr);
            $content.find('.output').html('Working...');

            var svg = new ExpDraw($content.find(".explanation svg")[0]);
            svg.prepare(checkioInput);

            if (data.ext) {
                var rightResult = data.ext["answer"];
                var userData = data.out;
                var userResult = userData[0];
                var userResultStr = userData[1];
                var result = data.ext["result"];
                var result_addon = data.ext["result_addon"];
                var result_message = result_addon[1];
                var result_show = result_addon[0];

                //if you need additional info from tests (if exists)
                var explanation = data.ext["explanation"];
                $content.find('.output').html(' Your result: ' + userResultStr);
                if (!result) {
                    $content.find('.answer').html(result_message);
                    $content.find('.answer').addClass('error');
                    $content.find('.output').addClass('error');
                    $content.find('.call').addClass('error');
                }
            }
            else {
                $content.find('.answer').remove();
            }

            //Your code here about test explanation animation
            //$content.find(".explanation").html("Something text for example");
            //
            //
            //
            //
            //

            this_e.setAnimationHeight($content.height() + 60);

        });

        //This is for Tryit (but not necessary)
//        var $tryit;
//        ext.set_console_process_ret(function (this_e, ret) {
//            $tryit.find(".checkio-result").html("Result
" + ret);
//        });
//
//        ext.set_generate_animation_panel(function (this_e) {
//            $tryit = $(this_e.setHtmlTryIt(ext.get_template('tryit'))).find('.tryit-content');
//            $tryit.find('.bn-check').click(function (e) {
//                e.preventDefault();
//                this_e.sendToConsoleCheckiO("something");
//            });
//        });
        function ExpDraw(dom) {
            var colorOrange4 = "#F0801A";
            var colorOrange3 = "#FA8F00";
            var colorOrange2 = "#FAA600";
            var colorOrange1 = "#FABA00";

            var colorBlue4 = "#294270";
            var colorBlue3 = "#006CA9";
            var colorBlue2 = "#65A1CF";
            var colorBlue1 = "#8FC7ED";

            var colorGrey4 = "#737370";
            var colorGrey3 = "#9D9E9E";
            var colorGrey2 = "#C5C6C6";
            var colorGrey1 = "#EBEDED";

            var colorWhite = "#FFFFFF";

            var p = 20;

            var minX = Infinity,
                minY = Infinity,
                maxX = 0,
                maxY = 0;

            var cell;

            var paper;

            var sizeX = 380;
            var sizeY = 380;

            var attrShadow = {stroke: colorGrey4, strokeWidth: 3, strokeLinecap: "round"};

            this.prepare = function(lines) {
                for (var i = 0; i < lines.length; i++) {
                    var line = lines[i];
                    minX = Math.min(minX, line[0], line[2]);
                    maxX = Math.max(maxX, line[0], line[2]);
                    minY = Math.min(minY, line[1], line[3]);
                    maxY = Math.max(maxY, line[1], line[3]);
                }
                var diff = Math.max(maxY - minY, maxX - minX);
                cell = (sizeX - 2 * p) / diff;
                paper = Snap(dom);
                sizeY = p * 2 + cell * (maxY - minY);
                paper.attr({width: sizeX, height: sizeY});

                for (i = 0; i < lines.length; i++) {
                    var segm = lines[i];
                    paper.line(
                        (segm[0] - minX) * cell + p,
                        sizeY - (segm[1] - minY) * cell - p,
                        (segm[2] - minX) * cell + p,
                        sizeY - (segm[3] - minY) * cell - p).attr(attrShadow);
                }

            }

        }

    }
);
<commit_msg>animation<commit_after>//Dont change it
requirejs(['ext_editor_1', 'jquery_190', 'raphael_210', 'snap.svg_030'],
    function (ext, $, Raphael, Snap) {

        var cur_slide = {};

        ext.set_start_game(function (this_e) {
        });

        ext.set_process_in(function (this_e, data) {
            cur_slide = {};
            cur_slide["in"] = data[0];
            this_e.addAnimationSlide(cur_slide);
        });

        ext.set_process_out(function (this_e, data) {
            cur_slide["out"] = data[0];
        });

        ext.set_process_ext(function (this_e, data) {
            cur_slide.ext = data;
        });

        ext.set_process_err(function (this_e, data) {
            cur_slide['error'] = data[0];
            this_e.addAnimationSlide(cur_slide);
            cur_slide = {};
        });

        ext.set_animate_success_slide(function (this_e, options) {
            var $h = $(this_e.setHtmlSlide('

'));
            this_e.setAnimationHeight(115);
        });

        ext.set_animate_slide(function (this_e, data, options) {
            var $content = $(this_e.setHtmlSlide(ext.get_template('animation'))).find('.animation-content');
            if (!data) {
                console.log("data is undefined");
                return false;
            }

            //YOUR FUNCTION NAME
            var fname = 'draw';

            var checkioInput = data.in;
            var checkioInputStr = fname + '(' +
                JSON.stringify(checkioInput).replace(/\[/g, "(").replace(/]/g, ")").replace("((", "{(").replace("))", ")}")
                + ')';

            var failError = function (dError) {
                $content.find('.call').html(checkioInputStr);
                $content.find('.output').html(dError.replace(/\n/g, ","));

                $content.find('.output').addClass('error');
                $content.find('.call').addClass('error');
                $content.find('.answer').remove();
                $content.find('.explanation').remove();
                this_e.setAnimationHeight($content.height() + 60);
            };

            if (data.error) {
                failError(data.error);
                return false;
            }

            if (data.ext && data.ext.inspector_fail) {
                failError(data.ext.inspector_result_addon);
                return false;
            }

            $content.find('.call').html(checkioInputStr);
            $content.find('.output').html('Working...');

            var svg = new ExpDraw($content.find(".explanation svg")[0]);
            svg.prepare(checkioInput);

            if (data.ext) {
                var rightResult = data.ext["answer"];
                var userData = data.out;
                var userResult = userData[0];
                var userResultStr = userData[1];
                var result = data.ext["result"];
                var result_addon = data.ext["result_addon"];
                var result_message = result_addon[1];
                var result_show = result_addon[0];

                //if you need additional info from tests (if exists)
                var explanation = data.ext["explanation"];
                $content.find('.output').html(' Your result: ' + userResultStr);
                if (!result) {
                    $content.find('.answer').html(result_message);
                    $content.find('.answer').addClass('error');
                    $content.find('.output').addClass('error');
                    $content.find('.call').addClass('error');
                }
                if (result_show) {
                    svg.animateLines(userResult);
                }
            }
            else {
                $content.find('.answer').remove();
            }

            //Your code here about test explanation animation
            //$content.find(".explanation").html("Something text for example");
            //
            //
            //
            //
            //

            this_e.setAnimationHeight($content.height() + 60);

        });

        //This is for Tryit (but not necessary)
//        var $tryit;
//        ext.set_console_process_ret(function (this_e, ret) {
//            $tryit.find(".checkio-result").html("Result
" + ret);
//        });
//
//        ext.set_generate_animation_panel(function (this_e) {
//            $tryit = $(this_e.setHtmlTryIt(ext.get_template('tryit'))).find('.tryit-content');
//            $tryit.find('.bn-check').click(function (e) {
//                e.preventDefault();
//                this_e.sendToConsoleCheckiO("something");
//            });
//        });
        function ExpDraw(dom) {
            var colorOrange4 = "#F0801A";
            var colorOrange3 = "#FA8F00";
            var colorOrange2 = "#FAA600";
            var colorOrange1 = "#FABA00";

            var colorBlue4 = "#294270";
            var colorBlue3 = "#006CA9";
            var colorBlue2 = "#65A1CF";
            var colorBlue1 = "#8FC7ED";

            var colorGrey4 = "#737370";
            var colorGrey3 = "#9D9E9E";
            var colorGrey2 = "#C5C6C6";
            var colorGrey1 = "#EBEDED";

            var colorWhite = "#FFFFFF";

            var p = 20;

            var minX = Infinity,
                minY = Infinity,
                maxX = 0,
                maxY = 0;

            var cell;

            var paper;

            var sizeX = 380;
            var sizeY = 380;

            var attrShadow = {stroke: colorGrey4, strokeWidth: 4, strokeLinecap: "round"};
            var attrLine = {stroke: colorOrange4, strokeWidth: 2, strokeLinecap: "round"};

            this.prepare = function (lines) {
                for (var i = 0; i < lines.length; i++) {
                    var line = lines[i];
                    minX = Math.min(minX, line[0], line[2]);
                    maxX = Math.max(maxX, line[0], line[2]);
                    minY = Math.min(minY, line[1], line[3]);
                    maxY = Math.max(maxY, line[1], line[3]);
                }
                var diff = Math.max(maxY - minY, maxX - minX);
                cell = (sizeX - 2 * p) / diff;
                paper = Snap(dom);
                sizeY = p * 2 + cell * (maxY - minY);
                paper.attr({width: sizeX, height: sizeY});

                for (i = 0; i < lines.length; i++) {
                    var segm = lines[i];
                    paper.line(
                        (segm[0] - minX) * cell + p,
                        sizeY - (segm[1] - minY) * cell - p,
                        (segm[2] - minX) * cell + p,
                        sizeY - (segm[3] - minY) * cell - p).attr(attrShadow);
                }

            };

            this.animateLines = function (points) {
                var count = 0;
                var stepTime = 2;
                (function draw() {
                    if (count >= points.length - 1) {
                        return false;
                    }
                    var f = points[count];
                    var s = points[count + 1];
                    var segm = paper.path(Snap.format(
                        "M{f0},{f1}L{f0},{f1}",
                        {
                            f0: (f[0] - minX) * cell + p,
                            f1: sizeY - (f[1] - minY) * cell - p})).attr(attrLine);
                var dist = Math.sqrt(Math.pow(f[0] - s[0], 2) + Math.pow(f[1] - s[1], 2)) * cell;
                segm.animate(
                    {"path": Snap.format("M{f0},{f1}L{s0},{s1}",
                        {
                            f0: (f[0] - minX) * cell + p,
                            f1: sizeY - (f[1] - minY) * cell - p,
                            s0: (s[0] - minX) * cell + p,
                            s1: sizeY - (s[1] - minY) * cell - p})}, dist * stepTime,
                    callback = function(){count++; draw()});

            }()
        )
            ;
        }
    }

}
)
;
<|endoftext|>


<commit_before>// ------------------------------------------------------------------------------------------------
// Simplenote Database interface.
// ------------------------------------------------------------------------------------------------

var SimplenoteDB = {
    offlineKey : "_offline",

    isDebug : true,

    log : function(s) {
        if (this.isDebug)
            logGeneral(s,"SimplenoteDB");
    },

    isOffline : function() {
        return $.storage.get(this.offlineKey) == "true";
    },

    offline : function(isOffline) {

        if(isOffline==undefined)
            throw("SimplenoteDB.offline: please query via .isOffline()");

        var oldIsOffline = this.isOffline();

        if (isOffline != oldIsOffline) {
            this.log("offline:mode change to offline=" + isOffline);
            uiEvent("offlinechanged", {status:isOffline});
            $.storage.set(this.offlineKey,isOffline==true);
        }
    },

    _reset : function() {
        this.isSyncInProgress = false;
        this.syncCallbackChunk = undefined;
        this.syncCallbackFinished = undefined;
        this._indexKeysTemp = [];
        this._indexKeysChanged = {hadchanges: false, added:[],changed:[],deleted:[]};
    },
    // jsut to detect whether we actually had a sync
    hadSync : function() {
        return localStorage[SimplenoteLS.indexTimeKey] != undefined;
    },

    sync : function(fullSync, callbackFinished, callbackChunk) {
        if (this.isSyncInProgress) {
            log("sync: sync already in progress, returning");
            return;
        }

        this.isSyncInProgress = true;
        this._reset();

        var syncKeys = SimplenoteLS.getSyncKeys();
        var note;

        this.log("sync: fullSync = " + fullSync);

        this.syncCallbackChunk = callbackChunk;
        this.syncCallbackFinished = callbackFinished;

        // push local changes
        $.each(syncKeys, function(i,key) {
            note = SimplenoteLS.getNote(key);
            if (note != undefined) {
                if (note.key.match(/creatednote(\d+)/))
                    SimplenoteDB.createNote(note, undefined, true);
                else
                    SimplenoteDB.updateNote(note, undefined, true);
            }
        });

        // get index
        var apioptions = {};
        if (!fullSync) {
            var indexTime = SimplenoteLS.indexTime();
            if (indexTime) {
                this.log("sync:using lastIndexTime from storage: " + dateAgo(indexTime));
                apioptions.since = indexTime;
            }
        }
        apioptions.length = 100;

        uiEvent("sync", {status: "started", changes : this._indexKeysChanged});

        this.getIndex(apioptions, fullSync);

    },
    //  count: 20
    //  data: Array[20]
    //  mark: "agtzaW1wbGUtbm90ZXINCxIETm90ZRiElc0HDB"
    //  time: "1302261452.277224"
    _gotIndexChunk : function(indexData, havemore, fullSync) {

        if (!indexData) {
            if (this.syncCallbackFinished)
                this.syncCallbackFinished(false);
            this.log("_gotIndexChunk: error getting index from server.");
            this.offline(true);
            this.isSyncInProgress = false;
            uiEvent("sync", { status: "error", changes : this._indexKeysChanged});
        } else {
            var thisHadChanges = false, note;

            for (var i=0; i<indexData.data.length; i++) {
                note = indexData.data[i];

                if (fullSync)
                    this._indexKeysTemp.push(note.key);

                if (!SimplenoteLS.haveNote(note.key)) {
                    SimplenoteLS.addNote(note);
                    this._indexKeysChanged.hadchanges = true;
                    this._indexKeysChanged.added.push(note.key);
                } else {
                    thisHadChanges = SimplenoteLS.updateNote(note,"index");
                    if (thisHadChanges.hadChanges) {
                        this._indexKeysChanged.hadchanges = true;
                        this._indexKeysChanged.changed.push(note.key);
                    }
                }
            }

            if (this.syncCallbackChunk)
                this.syncCallbackChunk(indexData);

            if (!havemore) {
                if (indexData.data.length > 0) // check for indextime sync, most often we get 0 back
                    SimplenoteLS.indexTime(indexData.time);

                if (fullSync) {
                    // check for deletions
                    var lskeys = SimplenoteLS.getKeys(), key;
                    this.log("_gotIndexChunk: checking for remote deletions (got " + SimplenoteDB._indexKeysTemp.length + " notes, have " + SimplenoteLS.getKeys().length + ")");
                    for (i=0; i<lskeys.length;i++) {
                        key = lskeys[i];
                        if (this._indexKeysTemp.indexOf(key)<0) {
                            SimplenoteLS.delNote(key);
                            this._indexKeysChanged.hadchanges = true;
                            this._indexKeysChanged.deleted.push(key);
                        }
                    }
                    if (this._indexKeysChanged.deleted.length > 0)
                        this.log("_gotIndexChunk: had remote deletions.");
                    else
                        this.log("_gotIndexChunk: no remote deletions.");
                }

                if (this.syncCallbackFinished)
                    this.syncCallbackFinished(true);

                this.isSyncInProgress = false;

                uiEvent("sync", {status: "done", changes : this._indexKeysChanged});
            }
        }

        if (!havemore) {
            if (this._indexKeysChanged.added.length > 0)
                this.log("_gotIndexChunk: added " + this._indexKeysChanged.added.length);
            if (this._indexKeysChanged.changed.length > 0)
                this.log("_gotIndexChunk: changed " + this._indexKeysChanged.changed.length);
            if (this._indexKeysChanged.deleted.length > 0)
                this.log("_gotIndexChunk: deleted " + this._indexKeysChanged.deleted.length);

            delete this._indexKeysChanged;
        }
    },

    // callback : function(noteData), called after every index chunk
    // mark : (optional) marked note for more
    getIndex : function(apioptions, fullSync) {

        var callbacks = {

            //  count: 20
            //  data: Array[20]
            //  mark: "agtzaW1wbGUtbm90ZXINCxIETm90ZRiElc0HDB"
            //  time: "1302261452.277224"
            success :       function(indexData) {
                SimplenoteDB.offline(false);

                SimplenoteDB.log("getIndex:success:got " + indexData.count + " keys from server.");

                if (indexData.mark != undefined) {
                    SimplenoteDB.log("getIndex:success:have mark, getting more.");
                    apioptions.mark = indexData.mark;
                    SimplenoteDB._gotIndexChunk(indexData, true, fullSync);

                    SimplenoteDB.getIndex(apioptions, fullSync);

                } else {
                    SimplenoteDB.log("getIndex:success:no mark, done.");
                    SimplenoteDB._gotIndexChunk(indexData, false, fullSync);
                }
            },
            loginInvalid:   function() {
                SimplenoteDB.log("getIndex:loginInvalid");
                alert("SimplenoteDB::getIndex:loginInvalid");
                SimplenoteDB._gotIndexChunk(undefined, false);
            },
            repeat:         function() {
                SimplenoteDB.log("getIndex:repeat");
                alert("SimplenoteDB::getIndex:repeat");
                SimplenoteDB._gotIndexChunk(undefined, false);
            },
            timeout:        function() {
                SimplenoteDB.log("getIndex:timeout");
                alert("SimplenoteDB::getIndex:timeout");
                SimplenoteDB._gotIndexChunk(undefined, false);
            }
        };

        // set defaults
        if (!apioptions) apioptions = {};
        if (!apioptions.length) apioptions.length = 100;

        SimplenoteAPI2.index(apioptions,callbacks);
    },

    getNote : function(key,callback) {
        var note = SimplenoteLS.getNote(key);
        if (note && note.content) {
            this.log("getNote:returning note from storage:" + key);
            if (callback)
                callback(note);
        } else {
            this.log("getNote:getting note from server:" + key);
            var callbacks = {
                success :       function(note) {
                    SimplenoteDB.offline(false);
                    SimplenoteLS.updateNote(note,"getnote");
                    if (callback)
                        callback(note);
                },
                loginInvalid:   function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::loginInvalid');
                    alert('SimplenoteDB::getNote::loginInvalid');
                    if (callback)
                        callback();
                },
                repeat:         function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::repeat');
                    alert('SimplenoteDB::getNote::repeat');
                    if (callback)
                        callback();
                },
                noteNotExist:  function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::noteNotExists');
                    alert('SimplenoteDB::getNote::noteNoteExist');
                    if (callback)
                        callback();
                },
                timeout: function(options) {
                    SimplenoteDB.offline(true);
                    SimplenoteDB.log('getNote::timeout');
                    alert('SimplenoteDB::getNote::timeout');
                    if (callback)
                        callback();
                }
            };
            SimplenoteAPI2.retrieve(key, callbacks);
        }
    },

    updateNote : function(data,callback,syncmode) {
        this.log("updateNote, syncMode = " + syncmode + " update data:");
        this.log(data);

        if (!syncmode) {
            // get local note version
            var note = SimplenoteLS.getNote(data.key);
            if (!note)
                throw "unknown or missing note, cannot update";

            delete data.action;

            // client updateable properties:
            if (data.content != undefined)      note.content = data.content;
            if (data.deleted != undefined)      note.deleted = data.deleted;
            if (data.tags != undefined)         note.tags = data.tags;
            if (data.systemtags != undefined)   note.systemtags = data.systemtags;

            if (data.content) {
                note.modifydate = (new Date())/1000;
                data.modifydate = note.modifydate;
                // needed for merging
                data.version = note.version;
            }

            SimplenoteLS.updateNote(note, "local");
            SimplenoteLS.addToSyncList(note.key);
        }

        var callbacks = {
            success :       function(note) {
                SimplenoteDB.offline(false);
                SimplenoteLS.updateNote(note,"updateresponse");
                SimplenoteLS.removeFromSyncList(note.key);
                if (callback)
                    callback(note);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::loginInvalid');
                alert('SimplenoteDB::updateNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::repeat');
                alert('SimplenoteDB::updateNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::noteNoteExists');
                alert('SimplenoteDB::updateNote::noteNoteExists');
            },
            timeout: function(note) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log('updateNote::timeout');
                alert('SimplenoteDB::updateNote::timeout');
                if (callback)
                    callback(note);
            }
        };
        SimplenoteAPI2.update(data, callbacks);

    },

    createNote : function(note,callback,syncmode) {
        this.log("createNote, note data below, syncmode = " +  syncmode);
        this.log(note2str(note,true));
        delete note.action;
        var tempkey;
        if (!syncmode) {
            note.createdate = (new Date())/1000;
            note.modifydate = note.createdate;
            // set things needed for operation
            tempkey = SimplenoteLS.getCreatedNoteTempKey();
            note.key = tempkey;
            note.deleted = 0;
            if (!note.tags)
                note.tags = [];
            if (!note.systemtags)
                note.systemtags = [];

            SimplenoteLS.addNote(note);
            SimplenoteLS.addToSyncList(note.key);
        }

        var callbacks = {
            success :       function(newnote) {
                SimplenoteDB.offline(false);

                newnote.content = note.content;
                SimplenoteLS.addNote(newnote);

                SimplenoteLS.delNote(tempkey);
                SimplenoteLS.removeFromSyncList(tempkey);

                if (callback)
                    callback(newnote);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::loginInvalid")
                alert('SimplenoteDB::createNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::repeat")
                alert('SimplenoteDB::createNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::noteNotExists")
                alert('SimplenoteDB::createNote::noteNotExists');
            },
            timeout: function(note) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log("createNote::timeout")
                alert('SimplenoteDB::createNote::timeout');
                if (callback)
                    callback();
            }
        };
        delete note.key; // remove temp key
        SimplenoteAPI2.create(note, callbacks);
    },

    deleteNote : function(key, callback) {
        var callbacks = {
            success :       function(data) {
                SimplenoteDB.offline(false);
                SimplenoteLS.delNote(key);
                if (callback)
                    callback(true);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::loginInvalid")
                alert('SimplenoteDB::deleteNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::repeat")
                alert('SimplenoteDB::deleteNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::noteNotExists")
                alert('SimplenoteDB::deleteNote::noteNotExists');
            },
            timeout: function(key) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log("deleteNote::timeout")
                alert('SimplenoteDB::deleteNote::timeout');
                if (callback)
                    callback(false);
            }
        };
        SimplenoteAPI2.destroy(key, callbacks);
    },

    _deleteAllRemote : function() {
        var note;
        $.each(SimplenoteLS.getKeys(), function(i,key) {
            note = SimplenoteLS.getNote(key);
            note.deleted = 1;
            SimplenoteDB.updateNote(note, function(note) {SimplenoteDB.deleteNote(note.key);}, true);
        });
    }
}

SimplenoteDB._reset();<commit_msg>tested + enabled offline mode<commit_after>// ------------------------------------------------------------------------------------------------
// Simplenote Database interface.
// ------------------------------------------------------------------------------------------------

var SimplenoteDB = {
    offlineKey : "_offline",

    isDebug : true,

    log : function(s) {
        if (this.isDebug)
            logGeneral(s,"SimplenoteDB");
    },

    isOffline : function() {
        return $.storage.get(this.offlineKey) == "true";
    },

    offline : function(isOffline) {

        if(isOffline==undefined)
            throw("SimplenoteDB.offline: please query via .isOffline()");

        var oldIsOffline = this.isOffline();

        if (isOffline != oldIsOffline) {
            this.log("offline:mode change to offline=" + isOffline);
            uiEvent("offlinechanged", {status:isOffline});
            $.storage.set(this.offlineKey,isOffline==true);
        }
    },

    _reset : function() {
        this.isSyncInProgress = false;
        this.syncCallbackChunk = undefined;
        this.syncCallbackFinished = undefined;
        this._indexKeysTemp = [];
        this._indexKeysChanged = {hadchanges: false, added:[],changed:[],deleted:[]};
    },
    // jsut to detect whether we actually had a sync
    hadSync : function() {
        return localStorage[SimplenoteLS.indexTimeKey] != undefined;
    },

    sync : function(fullSync, callbackFinished, callbackChunk) {
        if (this.isSyncInProgress) {
            log("sync: sync already in progress, returning");
            return;
        }

        this.isSyncInProgress = true;
        this._reset();

        var syncKeys = SimplenoteLS.getSyncKeys();
        var note;

        this.log("sync: fullSync = " + fullSync);

        this.syncCallbackChunk = callbackChunk;
        this.syncCallbackFinished = callbackFinished;

        // push local changes
        $.each(syncKeys, function(i,key) {
            note = SimplenoteLS.getNote(key);
            if (note != undefined) {
                if (note.key.match(/creatednote(\d+)/))
                    SimplenoteDB.createNote(note, undefined, true);
                else
                    SimplenoteDB.updateNote(note, undefined, true);
            }
        });

        // get index
        var apioptions = {};
        if (!fullSync) {
            var indexTime = SimplenoteLS.indexTime();
            if (indexTime) {
                this.log("sync:using lastIndexTime from storage: " + dateAgo(indexTime));
                apioptions.since = indexTime;
            }
        }
        apioptions.length = 100;

        uiEvent("sync", {status: "started", changes : this._indexKeysChanged});

        this.getIndex(apioptions, fullSync);

    },
    //  count: 20
    //  data: Array[20]
    //  mark: "agtzaW1wbGUtbm90ZXINCxIETm90ZRiElc0HDB"
    //  time: "1302261452.277224"
    _gotIndexChunk : function(indexData, havemore, fullSync) {

        if (!indexData) {
            if (this.syncCallbackFinished)
                this.syncCallbackFinished(false);
            this.log("_gotIndexChunk: error getting index from server.");
            this.offline(true);
            this.isSyncInProgress = false;
            uiEvent("sync", { status: "error", changes : this._indexKeysChanged});
        } else {
            var thisHadChanges = false, note;

            for (var i=0; i<indexData.data.length; i++) {
                note = indexData.data[i];

                if (fullSync)
                    this._indexKeysTemp.push(note.key);

                if (!SimplenoteLS.haveNote(note.key)) {
                    SimplenoteLS.addNote(note);
                    this._indexKeysChanged.hadchanges = true;
                    this._indexKeysChanged.added.push(note.key);
                } else {
                    thisHadChanges = SimplenoteLS.updateNote(note,"index");
                    if (thisHadChanges.hadChanges) {
                        this._indexKeysChanged.hadchanges = true;
                        this._indexKeysChanged.changed.push(note.key);
                    }
                }
            }

            if (this.syncCallbackChunk)
                this.syncCallbackChunk(indexData);

            if (!havemore) {
                if (indexData.data.length > 0) // check for indextime sync, most often we get 0 back
                    SimplenoteLS.indexTime(indexData.time);

                if (fullSync) {
                    // check for deletions
                    var lskeys = SimplenoteLS.getKeys(), key;
                    this.log("_gotIndexChunk: checking for remote deletions (got " + SimplenoteDB._indexKeysTemp.length + " notes, have " + SimplenoteLS.getKeys().length + ")");
                    for (i=0; i<lskeys.length;i++) {
                        key = lskeys[i];
                        if (this._indexKeysTemp.indexOf(key)<0) {
                            SimplenoteLS.delNote(key);
                            this._indexKeysChanged.hadchanges = true;
                            this._indexKeysChanged.deleted.push(key);
                        }
                    }
                    if (this._indexKeysChanged.deleted.length > 0)
                        this.log("_gotIndexChunk: had remote deletions.");
                    else
                        this.log("_gotIndexChunk: no remote deletions.");
                }

                if (this.syncCallbackFinished)
                    this.syncCallbackFinished(true);

                this.isSyncInProgress = false;

                uiEvent("sync", {status: "done", changes : this._indexKeysChanged});
            }
        }

        if (!havemore) {
            if (this._indexKeysChanged.added.length > 0)
                this.log("_gotIndexChunk: added " + this._indexKeysChanged.added.length);
            if (this._indexKeysChanged.changed.length > 0)
                this.log("_gotIndexChunk: changed " + this._indexKeysChanged.changed.length);
            if (this._indexKeysChanged.deleted.length > 0)
                this.log("_gotIndexChunk: deleted " + this._indexKeysChanged.deleted.length);

            delete this._indexKeysChanged;
        }
    },

    // callback : function(noteData), called after every index chunk
    // mark : (optional) marked note for more
    getIndex : function(apioptions, fullSync) {

        var callbacks = {

            //  count: 20
            //  data: Array[20]
            //  mark: "agtzaW1wbGUtbm90ZXINCxIETm90ZRiElc0HDB"
            //  time: "1302261452.277224"
            success :       function(indexData) {
                SimplenoteDB.offline(false);

                SimplenoteDB.log("getIndex:success:got " + indexData.count + " keys from server.");

                if (indexData.mark != undefined) {
                    SimplenoteDB.log("getIndex:success:have mark, getting more.");
                    apioptions.mark = indexData.mark;
                    SimplenoteDB._gotIndexChunk(indexData, true, fullSync);

                    SimplenoteDB.getIndex(apioptions, fullSync);

                } else {
                    SimplenoteDB.log("getIndex:success:no mark, done.");
                    SimplenoteDB._gotIndexChunk(indexData, false, fullSync);
                }
            },
            loginInvalid:   function() {
                SimplenoteDB.log("getIndex:loginInvalid");
                alert("SimplenoteDB::getIndex:loginInvalid");
                SimplenoteDB._gotIndexChunk(undefined, false);
            },
            repeat:         function() {
                SimplenoteDB.log("getIndex:repeat");
                alert("SimplenoteDB::getIndex:repeat");
                SimplenoteDB._gotIndexChunk(undefined, false);
            },
            timeout:        function() {
                SimplenoteDB.log("getIndex:timeout");
                SimplenoteDB.offline(true);
                //alert("SimplenoteDB::getIndex:timeout");
                SimplenoteDB._gotIndexChunk(undefined, false);
            }
        };

        // set defaults
        if (!apioptions) apioptions = {};
        if (!apioptions.length) apioptions.length = 100;

        SimplenoteAPI2.index(apioptions,callbacks);
    },

    getNote : function(key,callback) {
        var note = SimplenoteLS.getNote(key);
        if (note && note.content) {
            this.log("getNote:returning note from storage:" + key);
            if (callback)
                callback(note);
        } else {
            this.log("getNote:getting note from server:" + key);
            var callbacks = {
                success :       function(note) {
                    SimplenoteDB.offline(false);
                    SimplenoteLS.updateNote(note,"getnote");
                    if (callback)
                        callback(note);
                },
                loginInvalid:   function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::loginInvalid');
                    alert('SimplenoteDB::getNote::loginInvalid');
                    if (callback)
                        callback();
                },
                repeat:         function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::repeat');
                    alert('SimplenoteDB::getNote::repeat');
                    if (callback)
                        callback();
                },
                noteNotExist:  function() {
                    SimplenoteDB.offline(false);
                    SimplenoteDB.log('getNote::noteNotExists');
                    alert('SimplenoteDB::getNote::noteNoteExist');
                    if (callback)
                        callback();
                },
                timeout: function(options) {
                    SimplenoteDB.offline(true);
                    SimplenoteDB.log('getNote::timeout');
                    //alert('SimplenoteDB::getNote::timeout');
                    if (callback)
                        callback();
                }
            };
            SimplenoteAPI2.retrieve(key, callbacks);
        }
    },

    updateNote : function(data,callback,syncmode) {
        this.log("updateNote, syncMode = " + syncmode + " update data:");
        this.log(data);

        if (!syncmode) {
            // get local note version
            var note = SimplenoteLS.getNote(data.key);
            if (!note)
                throw "unknown or missing note, cannot update";

            delete data.action;

            // client updateable properties:
            if (data.content != undefined)      note.content = data.content;
            if (data.deleted != undefined)      note.deleted = data.deleted;
            if (data.tags != undefined)         note.tags = data.tags;
            if (data.systemtags != undefined)   note.systemtags = data.systemtags;

            if (data.content) {
                note.modifydate = (new Date())/1000;
                data.modifydate = note.modifydate;
                // needed for merging
                data.version = note.version;
            }

            SimplenoteLS.updateNote(note, "local");
            SimplenoteLS.addToSyncList(note.key);
        }

        var callbacks = {
            success :       function(note) {
                SimplenoteDB.offline(false);
                SimplenoteLS.updateNote(note,"updateresponse");
                SimplenoteLS.removeFromSyncList(note.key);
                if (callback)
                    callback(note);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::loginInvalid');
                alert('SimplenoteDB::updateNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::repeat');
                alert('SimplenoteDB::updateNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log('updateNote::noteNoteExists');
                alert('SimplenoteDB::updateNote::noteNoteExists');
            },
            timeout: function(note) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log('updateNote::timeout');
                //alert('SimplenoteDB::updateNote::timeout');
                if (callback)
                    callback(note);
            }
        };
        SimplenoteAPI2.update(data, callbacks);

    },

    createNote : function(note,callback,syncmode) {
        this.log("createNote, note data below, syncmode = " +  syncmode);
        this.log(note2str(note,true));
        delete note.action;
        var tempkey;
        if (!syncmode) {
            note.createdate = (new Date())/1000;
            note.modifydate = note.createdate;
            // set things needed for operation
            tempkey = SimplenoteLS.getCreatedNoteTempKey();
            note.key = tempkey;
            note.deleted = 0;
            if (!note.tags)
                note.tags = [];
            if (!note.systemtags)
                note.systemtags = [];

            SimplenoteLS.addNote(note);
            SimplenoteLS.addToSyncList(note.key);
        }

        var callbacks = {
            success :       function(newnote) {
                SimplenoteDB.offline(false);

                newnote.content = note.content;
                SimplenoteLS.addNote(newnote);

                SimplenoteLS.delNote(tempkey);
                SimplenoteLS.removeFromSyncList(tempkey);

                if (callback)
                    callback(newnote);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::loginInvalid")
                alert('SimplenoteDB::createNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::repeat")
                alert('SimplenoteDB::createNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("createNote::noteNotExists")
                alert('SimplenoteDB::createNote::noteNotExists');
            },
            timeout: function(note) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log("createNote::timeout")
                //alert('SimplenoteDB::createNote::timeout');
                if (callback)
                    callback();
            }
        };
        delete note.key; // remove temp key
        SimplenoteAPI2.create(note, callbacks);
    },

    deleteNote : function(key, callback) {
        var callbacks = {
            success :       function(data) {
                SimplenoteDB.offline(false);
                SimplenoteLS.delNote(key);
                if (callback)
                    callback(true);
            },
            loginInvalid:   function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::loginInvalid")
                alert('SimplenoteDB::deleteNote::loginInvalid');
            },
            repeat:         function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::repeat")
                alert('SimplenoteDB::deleteNote::repeat');
            },
            noteNotExists:  function() {
                SimplenoteDB.offline(false);
                SimplenoteDB.log("deleteNote::noteNotExists")
                alert('SimplenoteDB::deleteNote::noteNotExists');
            },
            timeout: function(key) {
                SimplenoteDB.offline(true);
                SimplenoteDB.log("deleteNote::timeout")
                alert('SimplenoteDB::deleteNote::timeout');
                if (callback)
                    callback(false);
            }
        };
        SimplenoteAPI2.destroy(key, callbacks);
    },

    _deleteAllRemote : function() {
        var note;
        $.each(SimplenoteLS.getKeys(), function(i,key) {
            note = SimplenoteLS.getNote(key);
            note.deleted = 1;
            SimplenoteDB.updateNote(note, function(note) {SimplenoteDB.deleteNote(note.key);}, true);
        });
    }
}

SimplenoteDB._reset();<|endoftext|>


<commit_before>import { WEBPACK_ELECTRON_PORT } from 'config';
import { app, BrowserWindow, dialog, shell, screen } from 'electron';
import isDev from 'electron-is-dev';
import windowStateKeeper from 'electron-window-state';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';

import setupBarMenu from './menu/setupBarMenu';
import * as PAGES from 'constants/pages';

export default appState => {
  // Get primary display dimensions from Electron.
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;

  // Load the previous state with fallback to defaults.
  const windowState = windowStateKeeper({
    defaultWidth: width,
    defaultHeight: height,
  });

  const startMinimized = (process.argv || []).includes('--hidden');

  const windowConfiguration = {
    backgroundColor: '#270f34', // Located in src/scss/init/_vars.scss `--color-background--splash`
    minWidth: 950,
    minHeight: 600,
    autoHideMenuBar: true,
    titleBarStyle: 'hiddenInset',
    show: false,
    // Create the window using the state information.
    x: windowState.x,
    y: windowState.y,
    // If state is undefined, create window as maximized.
    width: windowState.width === undefined ? width : windowState.width,
    height: windowState.height === undefined ? height : windowState.height,
    icon: 'static/img/tray/windows/tray.png',
    webPreferences: {
      // Disable renderer process's webSecurity on development to enable CORS.
      webSecurity: !isDev,
      plugins: true,
    },
  };
  const lbryProto = 'lbry://';
  const lbryProtoQ = 'lbry://?';
  const rendererURL = isDev ? `http://localhost:${WEBPACK_ELECTRON_PORT}` : `file://${__dirname}/index.html`;

  let window = new BrowserWindow(windowConfiguration);

  // Let us register listeners on the window, so we can update the state
  // automatically (the listeners will be removed when the window is closed)
  // and restore the maximized or full screen state.
  windowState.manage(window);

  let deepLinkingURI;

  if ((process.platform === 'win32' || process.platform === 'linux') && String(process.argv[1]).startsWith('lbry')) {
    [, deepLinkingURI] = process.argv || '';
    // Keep only command line / deep linked arguments
    // Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
    // restore the original URI that was typed.
    //   - If the URI has no path, Windows adds a trailing slash. LBRY URIs can't have a slash with no
    //     path, so we just strip it off.
    //   - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
    //     an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
    //   - ? also interpreted as an anchor, remove slash also.
    if (process.platform === 'win32') {
      deepLinkingURI = deepLinkingURI
        .replace(/\/$/, '')
        .replace('/#', '#')
        .replace('/?', '?');
    }
  } else {
    deepLinkingURI = appState.macDeepLinkingURI || '';
  }

  // is it a lbry://? pointing to an app page
  if (deepLinkingURI.includes(lbryProtoQ)) {
    let path = deepLinkingURI.substr(lbryProtoQ.length);
    let page = path.indexOf('?') >= 0 ? path.substring(0, path.indexOf('?')) : path;
    if (Object.values(PAGES).includes(page)) {
      deepLinkingURI = deepLinkingURI.replace(lbryProtoQ, '#/$/');
    } else {
      deepLinkingURI = '';
    }
    // else is it a claim
  } else if (deepLinkingURI.includes(lbryProto)) {
    deepLinkingURI = deepLinkingURI.replace(lbryProto, '#');
  } else {
    deepLinkingURI = '';
  }

  setupBarMenu();

  window.loadURL(rendererURL + deepLinkingURI);

  window.on('close', event => {
    if (!appState.isQuitting && !appState.autoUpdateAccepted) {
      event.preventDefault();
      if (window.isFullScreen()) {
        window.once('leave-full-screen', () => {
          window.hide();
        });
        window.setFullScreen(false);
      } else {
        window.hide();
      }
    }
  });

  window.on('focus', () => {
    window.webContents.send('window-is-focused', null);
  });

  window.on('unresponsive', () => {
    dialog.showMessageBox(
      window,
      {
        type: 'warning',
        buttons: ['Wait', 'Quit'],
        title: 'LBRY Unresponsive',
        defaultId: 1,
        message: 'LBRY is not responding. Would you like to quit?',
        cancelId: 0,
      },
      buttonIndex => {
        if (buttonIndex === 1) app.quit();
      }
    );
  });

  window.once('ready-to-show', () => {
    startMinimized ? window.hide() : window.show();
  });

  window.webContents.once('dom-ready', () => {
    startMinimized && window.hide()
  })

  window.webContents.on('did-finish-load', () => {
    window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);

    // restore the user's previous language - we have to do this from here because only electron process can access app.getLocale()
    window.webContents.executeJavaScript("localStorage.getItem('language')").then(storedLanguage => {
      const language =
        storedLanguage && storedLanguage !== 'undefined' && storedLanguage !== 'null'
          ? storedLanguage
          : app.getLocale().slice(0, 2);
      if (language !== 'en' && SUPPORTED_LANGUAGES[language]) {
        window.webContents.send('language-set', language);
      }
    });

    if (isDev) {
      window.webContents.openDevTools();
    }
  });

  window.webContents.on('crashed', () => {
    window = null;
  });

  window.webContents.on('new-window', (event, url) => {
    event.preventDefault();
    shell.openExternal(url);
  });

  return window;
};
<commit_msg>add comment<commit_after>import { WEBPACK_ELECTRON_PORT } from 'config';
import { app, BrowserWindow, dialog, shell, screen } from 'electron';
import isDev from 'electron-is-dev';
import windowStateKeeper from 'electron-window-state';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';

import setupBarMenu from './menu/setupBarMenu';
import * as PAGES from 'constants/pages';

export default appState => {
  // Get primary display dimensions from Electron.
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;

  // Load the previous state with fallback to defaults.
  const windowState = windowStateKeeper({
    defaultWidth: width,
    defaultHeight: height,
  });

  const startMinimized = (process.argv || []).includes('--hidden');

  const windowConfiguration = {
    backgroundColor: '#270f34', // Located in src/scss/init/_vars.scss `--color-background--splash`
    minWidth: 950,
    minHeight: 600,
    autoHideMenuBar: true,
    titleBarStyle: 'hiddenInset',
    show: false,
    // Create the window using the state information.
    x: windowState.x,
    y: windowState.y,
    // If state is undefined, create window as maximized.
    width: windowState.width === undefined ? width : windowState.width,
    height: windowState.height === undefined ? height : windowState.height,
    icon: 'static/img/tray/windows/tray.png',
    webPreferences: {
      // Disable renderer process's webSecurity on development to enable CORS.
      webSecurity: !isDev,
      plugins: true,
    },
  };
  const lbryProto = 'lbry://';
  const lbryProtoQ = 'lbry://?';
  const rendererURL = isDev ? `http://localhost:${WEBPACK_ELECTRON_PORT}` : `file://${__dirname}/index.html`;

  let window = new BrowserWindow(windowConfiguration);

  // Let us register listeners on the window, so we can update the state
  // automatically (the listeners will be removed when the window is closed)
  // and restore the maximized or full screen state.
  windowState.manage(window);

  let deepLinkingURI;

  if ((process.platform === 'win32' || process.platform === 'linux') && String(process.argv[1]).startsWith('lbry')) {
    [, deepLinkingURI] = process.argv || '';
    // Keep only command line / deep linked arguments
    // Windows normalizes URIs when they're passed in from other apps. On Windows, this tries to
    // restore the original URI that was typed.
    //   - If the URI has no path, Windows adds a trailing slash. LBRY URIs can't have a slash with no
    //     path, so we just strip it off.
    //   - In a URI with a claim ID, like lbry://channel#claimid, Windows interprets the hash mark as
    //     an anchor and converts it to lbry://channel/#claimid. We remove the slash here as well.
    //   - ? also interpreted as an anchor, remove slash also.
    if (process.platform === 'win32') {
      deepLinkingURI = deepLinkingURI
        .replace(/\/$/, '')
        .replace('/#', '#')
        .replace('/?', '?');
    }
  } else {
    deepLinkingURI = appState.macDeepLinkingURI || '';
  }

  // is it a lbry://? pointing to an app page
  if (deepLinkingURI.includes(lbryProtoQ)) {
    let path = deepLinkingURI.substr(lbryProtoQ.length);
    let page = path.indexOf('?') >= 0 ? path.substring(0, path.indexOf('?')) : path;
    if (Object.values(PAGES).includes(page)) {
      deepLinkingURI = deepLinkingURI.replace(lbryProtoQ, '#/$/');
    } else {
      deepLinkingURI = '';
    }
    // else is it a claim
  } else if (deepLinkingURI.includes(lbryProto)) {
    deepLinkingURI = deepLinkingURI.replace(lbryProto, '#');
  } else {
    deepLinkingURI = '';
  }

  setupBarMenu();

  window.loadURL(rendererURL + deepLinkingURI);

  window.on('close', event => {
    if (!appState.isQuitting && !appState.autoUpdateAccepted) {
      event.preventDefault();
      if (window.isFullScreen()) {
        window.once('leave-full-screen', () => {
          window.hide();
        });
        window.setFullScreen(false);
      } else {
        window.hide();
      }
    }
  });

  window.on('focus', () => {
    window.webContents.send('window-is-focused', null);
  });

  window.on('unresponsive', () => {
    dialog.showMessageBox(
      window,
      {
        type: 'warning',
        buttons: ['Wait', 'Quit'],
        title: 'LBRY Unresponsive',
        defaultId: 1,
        message: 'LBRY is not responding. Would you like to quit?',
        cancelId: 0,
      },
      buttonIndex => {
        if (buttonIndex === 1) app.quit();
      }
    );
  });

  window.once('ready-to-show', () => {
    startMinimized ? window.hide() : window.show();
  });

  // A backup incase https://github.com/electron/electron/issues/7779 happens
  window.webContents.once('dom-ready', () => {
    startMinimized && window.hide()
  })

  window.webContents.on('did-finish-load', () => {
    window.webContents.session.setUserAgent(`LBRY/${app.getVersion()}`);

    // restore the user's previous language - we have to do this from here because only electron process can access app.getLocale()
    window.webContents.executeJavaScript("localStorage.getItem('language')").then(storedLanguage => {
      const language =
        storedLanguage && storedLanguage !== 'undefined' && storedLanguage !== 'null'
          ? storedLanguage
          : app.getLocale().slice(0, 2);
      if (language !== 'en' && SUPPORTED_LANGUAGES[language]) {
        window.webContents.send('language-set', language);
      }
    });

    if (isDev) {
      window.webContents.openDevTools();
    }
  });

  window.webContents.on('crashed', () => {
    window = null;
  });

  window.webContents.on('new-window', (event, url) => {
    event.preventDefault();
    shell.openExternal(url);
  });

  return window;
};
<|endoftext|>


<commit_before>/*
 * jquery.couch.listchanges.js
 * A helper for using the CouchDB changes feed to update HTML views rendered from list funs
 *
 * Plays well with mustache.couch.js for server-side list fun rendering:
 * http://github.com/gbuesing/mustache.couch.js
 *
 * Copyright 2011, Geoff Buesing
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 *
 * Example:
 *
 *

 *
Foo

 *

 *
 *
 *
 * When data-changes="true", inner html of container element will be replaced with returned rows.
 * When data-changes="newRows", new rows will be prepended or appended to the container
 * as appropriate with the value of the descending querystring var.
 * "newRows" requires embedding the row key in each row in a data-key attribute.
 *
 * The list fun needs to return the entire page for a standard request, and just the templated rows
 * for XHR requests (rows_only=true is appended to the XHR querystring as a convenience.)
 *
 * See https://github.com/gbuesing/mustache.couch.js/tree/master/example
*/
(function( $ ){
  $.fn.listChanges = function(opts) {
    opts = opts || {};
    var container = this.eq(0);
    var update_seq = opts.update_seq || container.attr('data-update-seq');
    var type = opts.type || container.attr('data-changes');
    var dbname = unescape(window.location.pathname.split('/')[1]);
    var params = getQueryParams();
    params.rows_only = true;
    var descending = opts.descending || params.descending;
    var inFlight = false;

    var queryForUpdates = function() {
      var containerUpdateMethod = 'html';

      if (type === 'newRows') {
        delete params.limit;

        var highkeyElem = descending ? container.children('[data-key]:first') : container.children('[data-key]:last');
        var highkey = highkeyElem.attr('data-key');
        var highkey_docid = highkeyElem.attr('data-docid');

        if (descending) {
          containerUpdateMethod = 'prepend';

          if (highkey) {
            params.endkey = highkey;
            params.inclusive_end = false;
            if (highkey_docid) { params.endkey_docid = highkey_docid };
            delete params.startkey;
          }
        } else {
          containerUpdateMethod = 'append';

          if (highkey) {
            params.startkey = highkey;
            params.skip = 1;
            if (highkey_docid) { params.startkey_docid = highkey_docid };
            delete params.endkey;
          }
        }
      }

      $.ajax({
        type: 'GET',
        url: window.location.pathname,
        data: params,
        dataType: 'html',
        beforeSend: function() {
          if (inFlight) { return false };
          inFlight = true;
          return true;
        },
        success: function(data) {
          if (data && data.match(/\S/)) {
            var elem = $(data);
            container[containerUpdateMethod](elem);
            if (opts.success) { opts.success(elem, type) };
          }
          inFlight = false;
        },
        error: function() {
          inFlight = false;
        }
      });
    }

    $.couch.db(dbname).changes(update_seq).onChange(queryForUpdates);

    return container;
  }

  // helper fun
  function getQueryParams() {
    var params = {};
    if (window.location.search.length > 1) {
      for (var aItKey, nKeyId = 0, aCouples = window.location.search.substr(1).split("&"); nKeyId < aCouples.length; nKeyId++) {
        aItKey = aCouples[nKeyId].split("=");
        params[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : null;
      }
    }
    return params;
  }
})( jQuery );
<commit_msg>Need to move inFlight lock back to before we query DOM for highkey<commit_after>/*
 * jquery.couch.listchanges.js
 * A helper for using the CouchDB changes feed to update HTML views rendered from list funs
 *
 * Plays well with mustache.couch.js for server-side list fun rendering:
 * http://github.com/gbuesing/mustache.couch.js
 *
 * Copyright 2011, Geoff Buesing
 * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
 *
 * Example:
 *
 *

 *
Foo

 *

 *
 *
 *
 * When data-changes="true", inner html of container element will be replaced with returned rows.
 * When data-changes="newRows", new rows will be prepended or appended to the container
 * as appropriate with the value of the descending querystring var.
 * "newRows" requires embedding the row key in each row in a data-key attribute.
 *
 * The list fun needs to return the entire page for a standard request, and just the templated rows
 * for XHR requests (rows_only=true is appended to the XHR querystring as a convenience.)
 *
 * See https://github.com/gbuesing/mustache.couch.js/tree/master/example
*/
(function( $ ){
  $.fn.listChanges = function(opts) {
    opts = opts || {};
    var container = this.eq(0);
    var update_seq = opts.update_seq || container.attr('data-update-seq');
    var type = opts.type || container.attr('data-changes');
    var dbname = unescape(window.location.pathname.split('/')[1]);
    var params = getQueryParams();
    params.rows_only = true;
    var descending = opts.descending || params.descending;
    var inFlight = false;

    var queryForUpdates = function() {
      if (inFlight) { return false };
      inFlight = true;
      var containerUpdateMethod = 'html';

      if (type === 'newRows') {
        delete params.limit;

        var highkeyElem = descending ? container.children('[data-key]:first') : container.children('[data-key]:last');
        var highkey = highkeyElem.attr('data-key');
        var highkey_docid = highkeyElem.attr('data-docid');

        if (descending) {
          containerUpdateMethod = 'prepend';

          if (highkey) {
            params.endkey = highkey;
            params.inclusive_end = false;
            if (highkey_docid) { params.endkey_docid = highkey_docid };
            delete params.startkey;
          }
        } else {
          containerUpdateMethod = 'append';

          if (highkey) {
            params.startkey = highkey;
            params.skip = 1;
            if (highkey_docid) { params.startkey_docid = highkey_docid };
            delete params.endkey;
          }
        }
      }

      $.ajax({
        type: 'GET',
        url: window.location.pathname,
        data: params,
        dataType: 'html',
        success: function(data) {
          if (data && data.match(/\S/)) {
            var elem = $(data);
            container[containerUpdateMethod](elem);
            if (opts.success) { opts.success(elem, type) };
          }
          inFlight = false;
        },
        error: function() {
          inFlight = false;
        }
      });
    }

    $.couch.db(dbname).changes(update_seq).onChange(queryForUpdates);

    return container;
  }

  // helper fun
  function getQueryParams() {
    var params = {};
    if (window.location.search.length > 1) {
      for (var aItKey, nKeyId = 0, aCouples = window.location.search.substr(1).split("&"); nKeyId < aCouples.length; nKeyId++) {
        aItKey = aCouples[nKeyId].split("=");
        params[unescape(aItKey[0])] = aItKey.length > 1 ? unescape(aItKey[1]) : null;
      }
    }
    return params;
  }
})( jQuery );
<|endoftext|>