<commit_before>var expect = require('chai').expect,
    sinon = require('sinon');

describe('request url', function () {
    var testrun;

    describe('with lowercase', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'http://postman-echo.com/post?name=postman',
                            method: 'POST'
                        }
                    }, {
                        request: {
                            url: 'https://postman-echo.com/post?name=postman',
                            method: 'POST'
                        }
                    }]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the protocol and hostname for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should normalise the protocol and hostname for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/post?name=postman');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/post?name=postman');
        });
    });

    describe('with uppercase', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'HTTP://POSTMAN-ECHO.COM/POST?NAME=POSTMAN',
                            method: 'POST'
                        }
                    },
                    {
                        request: {
                            url: 'HTTPS://POSTMAN-ECHO.COM/POST?NAME=POSTMAN',
                            method: 'POST'
                        }
                    }
                    ]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the protocol and hostname for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should normalise the protocol and hostname for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/POST?NAME=POSTMAN');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/POST?NAME=POSTMAN');
        });
    });

    describe('with mixed case', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'HttP://POsTmaN-ecHo.CoM/PoST?NamE=PosTMaN',
                            method: 'POST'
                        }
                    },
                    {
                        request: {
                            url: 'htTpS://POsTmaN-eChO.Com/Post?NaMe=PostMaN',
                            method: 'POST'
                        }
                    }
                    ]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the protocol and hostname for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should normalise the protocol and hostname for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('https://postman-echo.com');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/PoST?NamE=PosTMaN');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/Post?NaMe=PostMaN');
        });
    });
});
<commit_msg>Modify Integration tests for url case sensitivity<commit_after>var expect = require('chai').expect,
    sinon = require('sinon');

describe('request url', function () {
    var testrun;

    describe('with lowercase', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'http://postman-echo.com/post?name=postman',
                            method: 'POST'
                        }
                    }, {
                        request: {
                            url: 'https://postman-echo.com/post?name=postman',
                            method: 'POST'
                        }
                    }]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the url for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/post?name=postman');
        });

        it('should normalise the url for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/post?name=postman');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/post?name=postman');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/post?name=postman');
        });
    });

    describe('with uppercase', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'HTTP://POSTMAN-ECHO.COM/POST?NAME=POSTMAN',
                            method: 'POST'
                        }
                    },
                    {
                        request: {
                            url: 'HTTPS://POSTMAN-ECHO.COM/POST?NAME=POSTMAN',
                            method: 'POST'
                        }
                    }
                    ]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the url for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/POST?NAME=POSTMAN');
        });

        it('should normalise the url for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/POST?NAME=POSTMAN');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/POST?NAME=POSTMAN');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/POST?NAME=POSTMAN');
        });
    });

    describe('with mixed case', function () {
        before(function (done) {
            this.run({
                collection: {
                    item: [{
                        request: {
                            url: 'HttP://POsTmaN-ecHo.CoM/PoST?NamE=PosTMaN',
                            method: 'POST'
                        }
                    },
                    {
                        request: {
                            url: 'htTpS://POsTmaN-eChO.Com/Post?NaMe=PostMaN',
                            method: 'POST'
                        }
                    }
                    ]
                }
            }, function (err, result) {
                testrun = result;
                done(err);
            });
        });

        it('should complete the run', function () {
            expect(testrun).to.be.ok;
            sinon.assert.calledOnce(testrun.start);
            sinon.assert.calledOnce(testrun.done);
            sinon.assert.calledTwice(testrun.request);
            sinon.assert.calledTwice(testrun.response);
            sinon.assert.calledWith(testrun.done.getCall(0), null);
        });

        it('should process the http request', function () {
            sinon.assert.calledWith(testrun.request.getCall(0), null);
            sinon.assert.calledWith(testrun.response.getCall(0), null);

            expect(testrun.response.getCall(0).args[2]).to.have.property('code', 200);
        });

        it('should process the https request', function () {
            sinon.assert.calledWith(testrun.request.getCall(1), null);
            sinon.assert.calledWith(testrun.response.getCall(1), null);

            expect(testrun.response.getCall(1).args[2]).to.have.property('code', 200);
        });

        it('should normalise the url for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/PoST?NamE=PosTMaN');
        });

        it('should normalise the url for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have
                .property('url', 'https://postman-echo.com/Post?NaMe=PostMaN');
        });

        it('should maintain the case sensitivity of path and query parameters for http', function () {
            var request = testrun.request.getCall(0).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/PoST?NamE=PosTMaN');
        });

        it('should maintain the case sensitivity of path and query parameters for https', function () {
            var request = testrun.request.getCall(1).args[2].stream.toString();

            expect(JSON.parse(request)).to.have.property('url')
                .that.have.string('/Post?NaMe=PostMaN');
        });
    });
});
<|endoftext|>


<commit_before>import assert from 'assert';
import sinon from 'sinon';
import {
  promiseThat,
  assertThat,
  fulfilled,
  isFulfilledWith
} from 'hamjest';

describe('', function() {

  let readFile;

  class File {}

  class JsonFile {
    constructor(readFile) {
      this.readFile = readFile;
      this.file = null;
    }
    read(file) {
      this.file = this.readFile(file);
      return this;
    }
    minify() {
      this.file = this.file.then(content => JSON.stringify(JSON.parse(content)));
      return this;
    }
  }

  describe('reads the file', function() {

    function readJsonFile(file) {
      return new JsonFile(readFile).read(file).minify().file;
    }

    it('and minifies the JSON', function() {
      const payload = {a:1};
      const beautifiedJson = JSON.stringify(payload, null, 4);
      const minifiedJson = JSON.stringify(payload);
      readFile = () => Promise.resolve(beautifiedJson);

      return promiseThat(readJsonFile(), isFulfilledWith(minifiedJson));
    });

    it('from the given path', function() {
      const payload = {a:1};
      const beautifiedJson = JSON.stringify(payload, null, 4);
      const file = new File();
      readFile = sinon.stub();
      readFile.returns(Promise.resolve(beautifiedJson));

      readJsonFile(file);

      sinon.assert.calledWith(readFile, file);
    });

  });

  describe('writes minified file to given ', function() {

  });

});
<commit_msg>Building the file writing.<commit_after>import assert from 'assert';
import sinon from 'sinon';
import {
  promiseThat,
  assertThat,
  fulfilled,
  isFulfilledWith
} from 'hamjest';

assert.called = sinon.assert.called;
assert.calledWith = sinon.assert.calledWith;

describe('', function() {

  let readFile;

  class File {}

  class JsonFile {
    constructor(readFile, writeFile) {
      this.readFile = readFile;
      this.writeFile = writeFile;
      this.fileContent = null;
    }
    read(file) {
      this.fileContent = this.readFile(file);
      return this;
    }
    minify() {
      this.fileContent = this.fileContent.then(content => JSON.stringify(JSON.parse(content)));
      return this;
    }
    write(file) {
      //this.fileContent.then(content => this.writeFile(file, content));
      return this.fileContent.then(content => this.writeFile(file, content));
    }
  }

  describe('reads the file', function() {

    function readJsonFile(file) {
      return new JsonFile(readFile).read(file).minify().fileContent;
    }

    it('and minifies the JSON', function() {
      const payload = {a:1};
      const beautifiedJson = JSON.stringify(payload, null, 4);
      const minifiedJson = JSON.stringify(payload);
      readFile = () => Promise.resolve(beautifiedJson);

      return promiseThat(readJsonFile(), isFulfilledWith(minifiedJson));
    });

    it('from the given path', function() {
      const payload = {a:1};
      const beautifiedJson = JSON.stringify(payload, null, 4);
      const file = new File();
      readFile = sinon.stub();
      readFile.returns(Promise.resolve(beautifiedJson));

      readJsonFile(file);

      assert.calledWith(readFile, file);
    });

  });

  describe('writes file content', function() {

    xit('to given path', function() {
      const fileContent = 'file content';
      const readFile = () => Promise.resolve(fileContent);
      const writeFile = sinon.stub();
      const file = new File();
      let jsonFile = new JsonFile(readFile, writeFile);

      return promiseThat(jsonFile.read().write(file), fulfilled());

      //assert.called(writeFile);
      //assert.calledWith(writeFile, file);
    });

  });

});
<|endoftext|>


<commit_before>/*
 * Leaflet (for image viewer), foundation (modals, tooltips) and jquery layers
 * on top of the core text viewer.
 *
 * See text_viewer.js for core logic (vue.js)
 *
*/
$(function() {
    var Foundation = window.Foundation;
    var L = window.L;

    // Note that 256 leaves small black lines between tiles.
    var image_viewer_tile_size = 512;

    window.image_modal = null;
    window.image_viewer_map = null;
    window.image_viewer_map_layer = null;

    $('body').on('click', 'figure', function() {
        destroy_tooltip();

        if (window.image_viewer_map) {
            window.image_viewer_map.remove();
            window.image_viewer_map = null;
            window.image_viewer_map_layer = null;
        }

        if (window.image_modal) {
            window.image_modal.destroy();
            window.image_modal = null;
        }

        var modal = window.image_modal = new Foundation.Reveal($('#image-viewer-modal'), {
            overlay: false,
        });

        modal.open();
        var $modal = modal.$element;
        $modal.css('display', 'flex');

        var map = window.image_viewer_map = L.map('image-viewer', {
            center: [0, 0],
            crs: L.CRS.Simple,
            zoom: 1,
        });

        var $img = $(this).find('img');
        var image_url = $img.attr('data-jp2');
        var title = $img.attr('title') || 'Image Viewer';
        var caption = $(this).find('figcaption').html() || '';
        // caption = "The temple lies in the center of a vast sanctuary, whose extent and complexity was revealed by excavations conducted from 2013 to 2016, on a site whose history goes back to Neolithic times, and which experienced an important phase of monumental constructions in the 1st Century CE. The temple was abandoned at the onset of the Early Middle Ages, and its structures were later reused in the fashioning of a Medieval defensive work. The temple has retained two sides of its square cella, at a height of over 20 meters, as well as vestiges of its ambulatory and side structure foundations. The temple's supposed dedication to the Roman god Janus is not based on any archaeological or historic fact, and the deity that was venerated in the temple is unknown.";

        $modal.find('h3').html(title);
        $modal.find('.image-viewer-caption').toggle(!!caption).html(caption);

        window.image_viewer_map_layer = L.tileLayer.iiif(
            window.SETTINGS_JS.IMAGE_SERVER_URL+image_url+'/info.json', {
                tileSize: image_viewer_tile_size,
                setMaxBounds: true,
            }
        ).addTo(map);
    });

    // Returns [xmin,ymin,xmax,ymax]
    // e.g, "19,26,42,42"
    // They represent the coordinates of the currently selected view
    // in the image viewer. Those coordinates are proportional
    // to the image size (in pc)
    /*
    window.image_viewer_map.getBounds()
        T {_southWest: M, _northEast: M}
        _northEast
        :
        M {lat: -187.64285278320312, lng: 204.98214721679688}
        _southWest
        :
        M {lat: -301.2678527832031, lng: 92.23214721679688}
        __proto__
        :
        Object

            window.get_relative_view_bounds()
        "19,26,42,41"
        window.set_relative_view_bounds("19,26,42,41")
    */
    function get_relative_view_bounds() {
        // Don't use this as bounds scale with zoom level!
        // var pbs = window.image_viewer_map.getPixelBounds();
        var bs = window.image_viewer_map.getBounds();
        var width = window.image_viewer_map_layer.x;
        var height = window.image_viewer_map_layer.y;
        ret = [
            window.image_viewer_map.project(bs.getNorthWest()),
            window.image_viewer_map.project(bs.getSouthEast()),
        ];
        var ret = [
            '',
            ret[0].x / width,
            ret[0].y / height,
            ret[1].x / width,
            ret[1].y / height,
        ];
        ret = ret.reduce(function(str, abs) {
            return (str ? str + ',' : '')+Math.round(abs*100);
        });

        return ret;
    }

    // opposite of get_ function, it takes something like
    // "19,26,42,42"
    // and returns LatLong bounds
    function set_relative_view_bounds(bounds_string) {
        var width = window.image_viewer_map_layer.x;
        var height = window.image_viewer_map_layer.y;
        var pcs = bounds_string.split(',');
        var vs = pcs.map(function(rel, idx) {
            return Math.round(parseInt(rel)/100.0*[width, height][idx % 2]);
        });
        var ret = L.latLngBounds(
            window.image_viewer_map.unproject([vs[0], vs[3]]),
            window.image_viewer_map.unproject([vs[2], vs[1]])
        );
        window.image_viewer_map.fitBounds(ret);
        return ret;
    }

    window.get_relative_view_bounds = get_relative_view_bounds;
    window.set_relative_view_bounds = set_relative_view_bounds;

    // note type="gloss"
    // .tei-note.tei-type-gloss > .note-text
    // See Dropbox 15_conversion of XML for the TExt viewer.docx
    var short_hands = window.SETTINGS_JS.SHORT_HANDS;

    function get_hand_label(hand_acronym) {
        var ret = '';
        ret = short_hands[hand_acronym || ''] || hand_acronym;
        return ret;
    }

    // TOOLTIPS
    // unclear
    window.tv_tooltip = null;

    function destroy_tooltip() {
        if (window.tv_tooltip) {
            window.tv_tooltip.destroy();
            window.tv_tooltip = null;
        }
    }

    function attach_tooltip(anchors, tooltip_fields_fct) {
        $('body').on('mouseenter mouseleave', anchors, function(ev) {
            if ($(this).parents('.text-conventions').length > 0) {
                return false;
            }
            destroy_tooltip();
            if (ev.type == 'mouseenter') {
                var tootlip_fields = tooltip_fields_fct($(this));

                var content = '';
                content = '
'+tootlip_fields.title+'
';
                content += '
'+tootlip_fields.body+'
';

                window.tv_tooltip = new Foundation.Tooltip($(this), {
                    tipText: content,
                    triggerClass: 'has-tip-tv',
                    tooltipClass: 'tooltip tv-tooltip',
                    positionClass: 'top',
                    allowHtml: true,
                });
                window.tv_tooltip.show();
            }
            return false;
        });
    }

    jQuery.expr[':'].notchildof = function(a,i,m){
        return jQuery(a).parents(m[3]).length < 1;
    };

    attach_tooltip('figure', function($el) {
        return {
            'title': 'Image (click to view)',
            'body': $el.find('img').attr('title'),
        };
    });
    attach_tooltip('.tv-view-semi-diplomatic .tei-pc-rend-6', function() {
        return {
            'title': 'Deux barres obliques',
            'body': 'probablement pour designer la position d’un pied-de-mouche',
        };
    });
    attach_tooltip('.tei-unclear', function() {
        return {
            'title': 'Texte illisible',
        };
    });
    attach_tooltip('.tei-corr', function($el) {
        return {
            'title': 'Correction',
            'body': 'ms. ' + $el.attr('data-sic'),
        };
    });
    attach_tooltip('.tei-add:notchildof(".tei-mod")', function($el) {
        return {
            'title': 'Addition',
            'body': 'Main: ' + get_hand_label($el.data('tei-hand')),
        };
    });
    attach_tooltip('.tv-view-semi-diplomatic .tei-mod', function($el) {
        return {
            'title': 'Modification',
            'body':
                'Contenu originel: ' +
                $el.find('.tei-del').html() +
                '
Main: ' +
                get_hand_label($el.data('tei-hand')),
        };
    });
    if ($('.tv-viewer-proofreader').length < 1) {
        attach_tooltip('.tei-note.tei-type-gloss', function($el) {
            return {
                'title': 'Note de lecteur médiéval',
                'body': '« ' + $el.find('.note-text').html() + ' »
' +
                        'Main: ' + get_hand_label($el.data('tei-resp'))
            };
        });
    }

    var reveal = null;
    $('body').on('click', '.tei-note.tei-type-note', function(ev) {
        var ret = true;
        if (reveal) {
            reveal.close();
        }
        if (ev.type == 'click') {
            var $reveal = $('#shared-reveal');
            if ($reveal.length) {
                $reveal.find('.body').html($(this).find('.note-text').html());

                var subtype_to_title = {
                    'source': 'Sources',
                    'trad':   'Tradition',
                    'gen':    'Note',
                    '':       'Note',
                };
                $reveal.find('h3').html(subtype_to_title[$(this).data('tei-subtype')] || 'Note');
                if (!reveal) {
                    reveal = new Foundation.Reveal($reveal, {
                        overlay: false,
                    });
                }
                reveal.open();
                ret = false;
            }
        }
        return ret;
    });
});
<commit_msg>click on image tooltip translation<commit_after>/*
 * Leaflet (for image viewer), foundation (modals, tooltips) and jquery layers
 * on top of the core text viewer.
 *
 * See text_viewer.js for core logic (vue.js)
 *
*/
$(function() {
    var Foundation = window.Foundation;
    var L = window.L;

    // Note that 256 leaves small black lines between tiles.
    var image_viewer_tile_size = 512;

    window.image_modal = null;
    window.image_viewer_map = null;
    window.image_viewer_map_layer = null;

    $('body').on('click', 'figure', function() {
        destroy_tooltip();

        if (window.image_viewer_map) {
            window.image_viewer_map.remove();
            window.image_viewer_map = null;
            window.image_viewer_map_layer = null;
        }

        if (window.image_modal) {
            window.image_modal.destroy();
            window.image_modal = null;
        }

        var modal = window.image_modal = new Foundation.Reveal($('#image-viewer-modal'), {
            overlay: false,
        });

        modal.open();
        var $modal = modal.$element;
        $modal.css('display', 'flex');

        var map = window.image_viewer_map = L.map('image-viewer', {
            center: [0, 0],
            crs: L.CRS.Simple,
            zoom: 1,
        });

        var $img = $(this).find('img');
        var image_url = $img.attr('data-jp2');
        var title = $img.attr('title') || 'Image Viewer';
        var caption = $(this).find('figcaption').html() || '';
        // caption = "The temple lies in the center of a vast sanctuary, whose extent and complexity was revealed by excavations conducted from 2013 to 2016, on a site whose history goes back to Neolithic times, and which experienced an important phase of monumental constructions in the 1st Century CE. The temple was abandoned at the onset of the Early Middle Ages, and its structures were later reused in the fashioning of a Medieval defensive work. The temple has retained two sides of its square cella, at a height of over 20 meters, as well as vestiges of its ambulatory and side structure foundations. The temple's supposed dedication to the Roman god Janus is not based on any archaeological or historic fact, and the deity that was venerated in the temple is unknown.";

        $modal.find('h3').html(title);
        $modal.find('.image-viewer-caption').toggle(!!caption).html(caption);

        window.image_viewer_map_layer = L.tileLayer.iiif(
            window.SETTINGS_JS.IMAGE_SERVER_URL+image_url+'/info.json', {
                tileSize: image_viewer_tile_size,
                setMaxBounds: true,
            }
        ).addTo(map);
    });

    // Returns [xmin,ymin,xmax,ymax]
    // e.g, "19,26,42,42"
    // They represent the coordinates of the currently selected view
    // in the image viewer. Those coordinates are proportional
    // to the image size (in pc)
    /*
    window.image_viewer_map.getBounds()
        T {_southWest: M, _northEast: M}
        _northEast
        :
        M {lat: -187.64285278320312, lng: 204.98214721679688}
        _southWest
        :
        M {lat: -301.2678527832031, lng: 92.23214721679688}
        __proto__
        :
        Object

            window.get_relative_view_bounds()
        "19,26,42,41"
        window.set_relative_view_bounds("19,26,42,41")
    */
    function get_relative_view_bounds() {
        // Don't use this as bounds scale with zoom level!
        // var pbs = window.image_viewer_map.getPixelBounds();
        var bs = window.image_viewer_map.getBounds();
        var width = window.image_viewer_map_layer.x;
        var height = window.image_viewer_map_layer.y;
        ret = [
            window.image_viewer_map.project(bs.getNorthWest()),
            window.image_viewer_map.project(bs.getSouthEast()),
        ];
        var ret = [
            '',
            ret[0].x / width,
            ret[0].y / height,
            ret[1].x / width,
            ret[1].y / height,
        ];
        ret = ret.reduce(function(str, abs) {
            return (str ? str + ',' : '')+Math.round(abs*100);
        });

        return ret;
    }

    // opposite of get_ function, it takes something like
    // "19,26,42,42"
    // and returns LatLong bounds
    function set_relative_view_bounds(bounds_string) {
        var width = window.image_viewer_map_layer.x;
        var height = window.image_viewer_map_layer.y;
        var pcs = bounds_string.split(',');
        var vs = pcs.map(function(rel, idx) {
            return Math.round(parseInt(rel)/100.0*[width, height][idx % 2]);
        });
        var ret = L.latLngBounds(
            window.image_viewer_map.unproject([vs[0], vs[3]]),
            window.image_viewer_map.unproject([vs[2], vs[1]])
        );
        window.image_viewer_map.fitBounds(ret);
        return ret;
    }

    window.get_relative_view_bounds = get_relative_view_bounds;
    window.set_relative_view_bounds = set_relative_view_bounds;

    // note type="gloss"
    // .tei-note.tei-type-gloss > .note-text
    // See Dropbox 15_conversion of XML for the TExt viewer.docx
    var short_hands = window.SETTINGS_JS.SHORT_HANDS;

    function get_hand_label(hand_acronym) {
        var ret = '';
        ret = short_hands[hand_acronym || ''] || hand_acronym;
        return ret;
    }

    // TOOLTIPS
    // unclear
    window.tv_tooltip = null;

    function destroy_tooltip() {
        if (window.tv_tooltip) {
            window.tv_tooltip.destroy();
            window.tv_tooltip = null;
        }
    }

    function attach_tooltip(anchors, tooltip_fields_fct) {
        $('body').on('mouseenter mouseleave', anchors, function(ev) {
            if ($(this).parents('.text-conventions').length > 0) {
                return false;
            }
            destroy_tooltip();
            if (ev.type == 'mouseenter') {
                var tootlip_fields = tooltip_fields_fct($(this));

                var content = '';
                content = '
'+tootlip_fields.title+'
';
                content += '
'+tootlip_fields.body+'
';

                window.tv_tooltip = new Foundation.Tooltip($(this), {
                    tipText: content,
                    triggerClass: 'has-tip-tv',
                    tooltipClass: 'tooltip tv-tooltip',
                    positionClass: 'top',
                    allowHtml: true,
                });
                window.tv_tooltip.show();
            }
            return false;
        });
    }

    jQuery.expr[':'].notchildof = function(a,i,m){
        return jQuery(a).parents(m[3]).length < 1;
    };

    attach_tooltip('figure', function($el) {
        return {
            'title': 'Image (click to view / cliquez pour voir)',
            'body': $el.find('img').attr('title'),
        };
    });
    attach_tooltip('.tv-view-semi-diplomatic .tei-pc-rend-6', function() {
        return {
            'title': 'Deux barres obliques',
            'body': 'probablement pour designer la position d’un pied-de-mouche',
        };
    });
    attach_tooltip('.tei-unclear', function() {
        return {
            'title': 'Texte illisible',
        };
    });
    attach_tooltip('.tei-corr', function($el) {
        return {
            'title': 'Correction',
            'body': 'ms. ' + $el.attr('data-sic'),
        };
    });
    attach_tooltip('.tei-add:notchildof(".tei-mod")', function($el) {
        return {
            'title': 'Addition',
            'body': 'Main: ' + get_hand_label($el.data('tei-hand')),
        };
    });
    attach_tooltip('.tv-view-semi-diplomatic .tei-mod', function($el) {
        return {
            'title': 'Modification',
            'body':
                'Contenu originel: ' +
                $el.find('.tei-del').html() +
                '
Main: ' +
                get_hand_label($el.data('tei-hand')),
        };
    });
    if ($('.tv-viewer-proofreader').length < 1) {
        attach_tooltip('.tei-note.tei-type-gloss', function($el) {
            return {
                'title': 'Note de lecteur médiéval',
                'body': '« ' + $el.find('.note-text').html() + ' »
' +
                        'Main: ' + get_hand_label($el.data('tei-resp'))
            };
        });
    }

    var reveal = null;
    $('body').on('click', '.tei-note.tei-type-note', function(ev) {
        var ret = true;
        if (reveal) {
            reveal.close();
        }
        if (ev.type == 'click') {
            var $reveal = $('#shared-reveal');
            if ($reveal.length) {
                $reveal.find('.body').html($(this).find('.note-text').html());

                var subtype_to_title = {
                    'source': 'Sources',
                    'trad':   'Tradition',
                    'gen':    'Note',
                    '':       'Note',
                };
                $reveal.find('h3').html(subtype_to_title[$(this).data('tei-subtype')] || 'Note');
                if (!reveal) {
                    reveal = new Foundation.Reveal($reveal, {
                        overlay: false,
                    });
                }
                reveal.open();
                ret = false;
            }
        }
        return ret;
    });
});
<|endoftext|>


<commit_before>import _ from "lodash"
import PropTypes from "prop-types"
import {v4 as uuid4} from "uuid"
import {errorValidator, mandatoryValueValidator} from "./editors/Validators"
import LabeledInput from "./editors/field/LabeledInput"
import LabeledTextarea from "./editors/field/LabeledTextarea"
import Map from "./editors/map/Map"

const MapVariable = ({isMarked, node, removeElement, addElement, onChange, readOnly, showValidation, errors, renderFieldLabel, variableTypes}) => {

  const addField = () => {
    addElement("fields", {name: "", uuid: uuid4(), expression: {expression: "", language: "spel"}})
  }

  const onInputChange = (path, event) => onChange(path, event.target.value)

  return (

      <LabeledInput
        renderFieldLabel={() => renderFieldLabel("Name")}
        value={node.id}
        onChange={(event) => onInputChange("id", event)}
        isMarked={isMarked("id")}
        readOnly={readOnly}
        showValidation={showValidation}
        validators={[mandatoryValueValidator]}
      />

      <LabeledInput
        renderFieldLabel={() => renderFieldLabel("Variable Name")}
        value={node.varName}
        onChange={(event) => onInputChange("varName", event)}
        isMarked={isMarked("varName")}
        readOnly={readOnly}
        showValidation={showValidation}
        validators={[mandatoryValueValidator, errorValidator(errors, "varName")]}
      />

      <Map
        label="Fields"
        onChange={onChange}
        fields={node.fields}
        removeField={removeElement}
        namespace="fields"
        addField={addField}
        isMarked={isMarked}
        readOnly={readOnly}
        showValidation={showValidation}
        variableTypes={variableTypes}
        showSwitch={false}
        errors={errors}
      />

      <LabeledTextarea
        renderFieldLabel={() => renderFieldLabel("Description")}
        value={_.get(node, "additionalFields.description", "")}
        onChange={(event) => onInputChange("additionalFields.description", event)}
        isMarked={isMarked("additionalFields.description")}
        readOnly={readOnly}
        className={"node-input"}
      />

  )
}

MapVariable.propTypes = {
  isMarked: PropTypes.func.isRequired,
  node: PropTypes.object.isRequired,
  removeElement: PropTypes.func.isRequired,
  addElement: PropTypes.func.isRequired,
  onChange: PropTypes.func.isRequired,
  readOnly: PropTypes.bool,
  showValidation: PropTypes.bool.isRequired,
  errors: PropTypes.array.isRequired,
  variableTypes: PropTypes.object.isRequired,
  renderFieldLabel: PropTypes.func.isRequired,
}

MapVariable.defaultProps = {
  readOnly: false,
}

MapVariable.availableFields = (node) => {
  return ["id", "varName"]
}

export default MapVariable
<commit_msg>[FIX] Add missing import in MapVariable (#1094)<commit_after>import _ from "lodash"
import PropTypes from "prop-types"
import {v4 as uuid4} from "uuid"
import {errorValidator, mandatoryValueValidator} from "./editors/Validators"
import LabeledInput from "./editors/field/LabeledInput"
import LabeledTextarea from "./editors/field/LabeledTextarea"
import Map from "./editors/map/Map"
import React from "react"

const MapVariable = ({isMarked, node, removeElement, addElement, onChange, readOnly, showValidation, errors, renderFieldLabel, variableTypes}) => {

  const addField = () => {
    addElement("fields", {name: "", uuid: uuid4(), expression: {expression: "", language: "spel"}})
  }

  const onInputChange = (path, event) => onChange(path, event.target.value)

  return (

      <LabeledInput
        renderFieldLabel={() => renderFieldLabel("Name")}
        value={node.id}
        onChange={(event) => onInputChange("id", event)}
        isMarked={isMarked("id")}
        readOnly={readOnly}
        showValidation={showValidation}
        validators={[mandatoryValueValidator]}
      />

      <LabeledInput
        renderFieldLabel={() => renderFieldLabel("Variable Name")}
        value={node.varName}
        onChange={(event) => onInputChange("varName", event)}
        isMarked={isMarked("varName")}
        readOnly={readOnly}
        showValidation={showValidation}
        validators={[mandatoryValueValidator, errorValidator(errors, "varName")]}
      />

      <Map
        label="Fields"
        onChange={onChange}
        fields={node.fields}
        removeField={removeElement}
        namespace="fields"
        addField={addField}
        isMarked={isMarked}
        readOnly={readOnly}
        showValidation={showValidation}
        variableTypes={variableTypes}
        showSwitch={false}
        errors={errors}
      />

      <LabeledTextarea
        renderFieldLabel={() => renderFieldLabel("Description")}
        value={_.get(node, "additionalFields.description", "")}
        onChange={(event) => onInputChange("additionalFields.description", event)}
        isMarked={isMarked("additionalFields.description")}
        readOnly={readOnly}
        className={"node-input"}
      />

  )
}

MapVariable.propTypes = {
  isMarked: PropTypes.func.isRequired,
  node: PropTypes.object.isRequired,
  removeElement: PropTypes.func.isRequired,
  addElement: PropTypes.func.isRequired,
  onChange: PropTypes.func.isRequired,
  readOnly: PropTypes.bool,
  showValidation: PropTypes.bool.isRequired,
  errors: PropTypes.array.isRequired,
  variableTypes: PropTypes.object.isRequired,
  renderFieldLabel: PropTypes.func.isRequired,
}

MapVariable.defaultProps = {
  readOnly: false,
}

MapVariable.availableFields = (node) => {
  return ["id", "varName"]
}

export default MapVariable
<|endoftext|>


<commit_before>/*
Copyright 2014 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

/*
This service handles what should happen when you get an event. This service does
not care where the event came from, it only needs enough context to be able to
process them. Events may be coming from the event stream, the REST API (via
direct GETs or via a pagination stream API), etc.

Typically, this service will store events or broadcast them to any listeners
(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
if typically all the $on method would do is update its own $scope.
*/
angular.module('eventHandlerService', [])
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', function(matrixService, $rootScope, $q) {
    var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
    var MSG_EVENT = "MSG_EVENT";
    var MEMBER_EVENT = "MEMBER_EVENT";
    var PRESENCE_EVENT = "PRESENCE_EVENT";
    var POWERLEVEL_EVENT = "POWERLEVEL_EVENT";
    var CALL_EVENT = "CALL_EVENT";
    var NAME_EVENT = "NAME_EVENT";

    var InitialSyncDeferred = $q.defer();

    $rootScope.events = {
        rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
    };

    $rootScope.presence = {};

    var initRoom = function(room_id) {
        if (!(room_id in $rootScope.events.rooms)) {
            console.log("Creating new handler entry for " + room_id);
            $rootScope.events.rooms[room_id] = {};
            $rootScope.events.rooms[room_id].messages = [];
            $rootScope.events.rooms[room_id].members = {};
        }
    };

    var resetRoomMessages = function(room_id) {
        if ($rootScope.events.rooms[room_id]) {
            $rootScope.events.rooms[room_id].messages = [];
        }
    };

    var handleRoomCreate = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // For now, we do not use the event data. Simply signal it to the app controllers
        $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
    };

    var handleRoomAliases = function(event, isLiveEvent) {
        matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
    };

    var handleMessage = function(event, isLiveEvent) {
        initRoom(event.room_id);

        if (isLiveEvent) {
            if (event.user_id === matrixService.config().user_id) {
                // assume we've already echoed it
                // FIXME: track events by ID and ungrey the right message to show it's been delivered
            }
            else {
                $rootScope.events.rooms[event.room_id].messages.push(event);
            }
        }
        else {
            $rootScope.events.rooms[event.room_id].messages.unshift(event);
        }

        // TODO send delivery receipt if isLiveEvent

        // $broadcast this, as controllers may want to do funky things such as
        // scroll to the bottom, etc which cannot be expressed via simple $scope
        // updates.
        $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
    };

    var handleRoomMember = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // if the server is stupidly re-relaying a no-op join, discard it.
        if (event.prev_content &&
            event.content.membership === "join" &&
            event.content.membership === event.prev_content.membership)
        {
            return;
        }

        // add membership changes as if they were a room message if something interesting changed
        if (event.content.prev !== event.content.membership) {
            if (isLiveEvent) {
                $rootScope.events.rooms[event.room_id].messages.push(event);
            }
            else {
                $rootScope.events.rooms[event.room_id].messages.unshift(event);
            }
        }

        $rootScope.events.rooms[event.room_id].members[event.state_key] = event;
        $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
    };

    var handlePresence = function(event, isLiveEvent) {
        $rootScope.presence[event.content.user_id] = event;
        $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent);
    };

    var handlePowerLevels = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // Keep the latest data. Do not care of events that come when paginating back
        if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
            $rootScope.events.rooms[event.room_id][event.type] = event;
            $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
        }
    };

    var handleRoomName = function(event, isLiveEvent) {
        console.log("handleRoomName " + isLiveEvent);

        initRoom(event.room_id);

        $rootScope.events.rooms[event.room_id][event.type] = event;
        $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
    };

    var handleCallEvent = function(event, isLiveEvent) {
        $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
    };

    return {
        ROOM_CREATE_EVENT: ROOM_CREATE_EVENT,
        MSG_EVENT: MSG_EVENT,
        MEMBER_EVENT: MEMBER_EVENT,
        PRESENCE_EVENT: PRESENCE_EVENT,
        POWERLEVEL_EVENT: POWERLEVEL_EVENT,
        CALL_EVENT: CALL_EVENT,
        NAME_EVENT: NAME_EVENT,

        handleEvent: function(event, isLiveEvent) {
            switch(event.type) {
                case "m.room.create":
                    handleRoomCreate(event, isLiveEvent);
                    break;
                case "m.room.aliases":
                    handleRoomAliases(event, isLiveEvent);
                    break;
                case "m.room.message":
                    handleMessage(event, isLiveEvent);
                    break;
                case "m.room.member":
                    handleRoomMember(event, isLiveEvent);
                    break;
                case "m.presence":
                    handlePresence(event, isLiveEvent);
                    break;
                case 'm.room.ops_levels':
                case 'm.room.send_event_level':
                case 'm.room.add_state_level':
                case 'm.room.join_rules':
                case 'm.room.power_levels':
                    handlePowerLevels(event, isLiveEvent);
                    break;
                case 'm.room.name':
                    handleRoomName(event, isLiveEvent);
                    break;
                default:
                    console.log("Unable to handle event type " + event.type);
                    console.log(JSON.stringify(event, undefined, 4));
                    break;
            }
            if (event.type.indexOf('m.call.') === 0) {
                handleCallEvent(event, isLiveEvent);
            }
        },

        // isLiveEvents determines whether notifications should be shown, whether
        // messages get appended to the start/end of lists, etc.
        handleEvents: function(events, isLiveEvents) {
            for (var i=0; i<events.length; i++) {
                this.handleEvent(events[i], isLiveEvents);
            }
        },

        handleInitialSyncDone: function() {
            console.log("# handleInitialSyncDone");
            InitialSyncDeferred.resolve($rootScope.events, $rootScope.presence);
        },

        // Returns a promise that resolves when the initialSync request has been processed
        waitForInitialSyncCompletion: function() {
            return InitialSyncDeferred.promise;
        },

        resetRoomMessages: function(room_id) {
            resetRoomMessages(room_id);
        }
    };
}]);
<commit_msg>dedup all events<commit_after>/*
Copyright 2014 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

'use strict';

/*
This service handles what should happen when you get an event. This service does
not care where the event came from, it only needs enough context to be able to
process them. Events may be coming from the event stream, the REST API (via
direct GETs or via a pagination stream API), etc.

Typically, this service will store events or broadcast them to any listeners
(e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope
if typically all the $on method would do is update its own $scope.
*/
angular.module('eventHandlerService', [])
.factory('eventHandlerService', ['matrixService', '$rootScope', '$q', function(matrixService, $rootScope, $q) {
    var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT";
    var MSG_EVENT = "MSG_EVENT";
    var MEMBER_EVENT = "MEMBER_EVENT";
    var PRESENCE_EVENT = "PRESENCE_EVENT";
    var POWERLEVEL_EVENT = "POWERLEVEL_EVENT";
    var CALL_EVENT = "CALL_EVENT";
    var NAME_EVENT = "NAME_EVENT";

    var InitialSyncDeferred = $q.defer();

    $rootScope.events = {
        rooms: {} // will contain roomId: { messages:[], members:{userid1: event} }
    };

    // used for dedupping events - could be expanded in future...
    // FIXME: means that we leak memory over time (along with lots of the rest
    // of the app, given we never try to reap memory yet)
    var eventMap = {};

    $rootScope.presence = {};

    var initRoom = function(room_id) {
        if (!(room_id in $rootScope.events.rooms)) {
            console.log("Creating new handler entry for " + room_id);
            $rootScope.events.rooms[room_id] = {};
            $rootScope.events.rooms[room_id].messages = [];
            $rootScope.events.rooms[room_id].members = {};
        }
    };

    var resetRoomMessages = function(room_id) {
        if ($rootScope.events.rooms[room_id]) {
            $rootScope.events.rooms[room_id].messages = [];
        }
    };

    var handleRoomCreate = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // For now, we do not use the event data. Simply signal it to the app controllers
        $rootScope.$broadcast(ROOM_CREATE_EVENT, event, isLiveEvent);
    };

    var handleRoomAliases = function(event, isLiveEvent) {
        matrixService.createRoomIdToAliasMapping(event.room_id, event.content.aliases[0]);
    };

    var handleMessage = function(event, isLiveEvent) {
        initRoom(event.room_id);

        if (isLiveEvent) {
            if (event.user_id === matrixService.config().user_id) {
                // assume we've already echoed it
                // FIXME: track events by ID and ungrey the right message to show it's been delivered
            }
            else {
                $rootScope.events.rooms[event.room_id].messages.push(event);
            }
        }
        else {
            $rootScope.events.rooms[event.room_id].messages.unshift(event);
        }

        // TODO send delivery receipt if isLiveEvent

        // $broadcast this, as controllers may want to do funky things such as
        // scroll to the bottom, etc which cannot be expressed via simple $scope
        // updates.
        $rootScope.$broadcast(MSG_EVENT, event, isLiveEvent);
    };

    var handleRoomMember = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // if the server is stupidly re-relaying a no-op join, discard it.
        if (event.prev_content &&
            event.content.membership === "join" &&
            event.content.membership === event.prev_content.membership)
        {
            return;
        }

        // add membership changes as if they were a room message if something interesting changed
        if (event.content.prev !== event.content.membership) {
            if (isLiveEvent) {
                $rootScope.events.rooms[event.room_id].messages.push(event);
            }
            else {
                $rootScope.events.rooms[event.room_id].messages.unshift(event);
            }
        }

        $rootScope.events.rooms[event.room_id].members[event.state_key] = event;
        $rootScope.$broadcast(MEMBER_EVENT, event, isLiveEvent);
    };

    var handlePresence = function(event, isLiveEvent) {
        $rootScope.presence[event.content.user_id] = event;
        $rootScope.$broadcast(PRESENCE_EVENT, event, isLiveEvent);
    };

    var handlePowerLevels = function(event, isLiveEvent) {
        initRoom(event.room_id);

        // Keep the latest data. Do not care of events that come when paginating back
        if (!$rootScope.events.rooms[event.room_id][event.type] || isLiveEvent) {
            $rootScope.events.rooms[event.room_id][event.type] = event;
            $rootScope.$broadcast(POWERLEVEL_EVENT, event, isLiveEvent);
        }
    };

    var handleRoomName = function(event, isLiveEvent) {
        console.log("handleRoomName " + isLiveEvent);

        initRoom(event.room_id);

        $rootScope.events.rooms[event.room_id][event.type] = event;
        $rootScope.$broadcast(NAME_EVENT, event, isLiveEvent);
    };

    var handleCallEvent = function(event, isLiveEvent) {
        $rootScope.$broadcast(CALL_EVENT, event, isLiveEvent);
    };

    return {
        ROOM_CREATE_EVENT: ROOM_CREATE_EVENT,
        MSG_EVENT: MSG_EVENT,
        MEMBER_EVENT: MEMBER_EVENT,
        PRESENCE_EVENT: PRESENCE_EVENT,
        POWERLEVEL_EVENT: POWERLEVEL_EVENT,
        CALL_EVENT: CALL_EVENT,
        NAME_EVENT: NAME_EVENT,

        handleEvent: function(event, isLiveEvent) {
            if (eventMap[event.event_id]) {
                console.log("discarding duplicate event: " + JSON.stringify(event));
                return;
            }

            if (event.type.indexOf('m.call.') === 0) {
                handleCallEvent(event, isLiveEvent);
            }
            else {
                switch(event.type) {
                    case "m.room.create":
                        handleRoomCreate(event, isLiveEvent);
                        break;
                    case "m.room.aliases":
                        handleRoomAliases(event, isLiveEvent);
                        break;
                    case "m.room.message":
                        handleMessage(event, isLiveEvent);
                        break;
                    case "m.room.member":
                        handleRoomMember(event, isLiveEvent);
                        break;
                    case "m.presence":
                        handlePresence(event, isLiveEvent);
                        break;
                    case 'm.room.ops_levels':
                    case 'm.room.send_event_level':
                    case 'm.room.add_state_level':
                    case 'm.room.join_rules':
                    case 'm.room.power_levels':
                        handlePowerLevels(event, isLiveEvent);
                        break;
                    case 'm.room.name':
                        handleRoomName(event, isLiveEvent);
                        break;
                    default:
                        console.log("Unable to handle event type " + event.type);
                        console.log(JSON.stringify(event, undefined, 4));
                        break;
                }
            }
        },

        // isLiveEvents determines whether notifications should be shown, whether
        // messages get appended to the start/end of lists, etc.
        handleEvents: function(events, isLiveEvents) {
            for (var i=0; i<events.length; i++) {
                this.handleEvent(events[i], isLiveEvents);
            }
        },

        handleInitialSyncDone: function() {
            console.log("# handleInitialSyncDone");
            InitialSyncDeferred.resolve($rootScope.events, $rootScope.presence);
        },

        // Returns a promise that resolves when the initialSync request has been processed
        waitForInitialSyncCompletion: function() {
            return InitialSyncDeferred.promise;
        },

        resetRoomMessages: function(room_id) {
            resetRoomMessages(room_id);
        }
    };
}]);
<|endoftext|>


<commit_before>angular.module('os.biospecimen.cp')
  .controller('CpContainerSettingsCtrl', function($scope, $translate, cp, Container) {
    function init() {
      $scope.contSettingsCtx = {}
      setViewCtx();
    }

    function setViewCtx() {
      angular.extend($scope.contSettingsCtx, {view: 'view_settings', cp: cp});
      setStrategy();
    }

    function setStrategy() {
      $translate('cp.container.title').then(
        function() {
          if (cp.containerSelectionStrategy) {
            var alloc = cp.containerSelectionStrategy.replace(/-/g, '_');
            $scope.contSettingsCtx.strategy = $translate.instant('container.alloc_strategies.' + alloc);
          } else {
            $scope.contSettingsCtx.strategy = $translate.instant('common.not_specified');
          }
        }
      );
    }

    function setEditCtx() {
      var ctx = {view: 'edit_settings', cp: angular.copy(cp)};
      angular.extend($scope.contSettingsCtx, ctx);
      loadStrategies();
    }

    function loadStrategies() {
      if ($scope.contSettingsCtx.allocStrategies) {
        return;
      }

      Container.getAutoAllocStrategies().then(
        function(strategies) {
          $scope.contSettingsCtx.allocStrategies = strategies;
        }
      );
    }

    $scope.showEditForm = function() {
      setEditCtx();
    }

    $scope.revertEdit = function() {
      setViewCtx();
    }

    $scope.clearStoreAliquots = function() {
      if (!$scope.contSettingsCtx.cp.containerSelectionStrategy) {
        $scope.contSettingsCtx.cp.aliquotsInSameContainer = false;
      }
    }

    $scope.save = function() {
      delete $scope.contSettingsCtx.cp.repositoryNames;
      delete $scope.contSettingsCtx.cp.extensionDetail;
      delete $scope.contSettingsCtx.cp.catalogSetting;

      $scope.contSettingsCtx.cp.$saveOrUpdate().then(
        function(savedCp) {
          angular.extend(cp, angular.extend($scope.cp, savedCp));
          setViewCtx();
        }
      );
    }

    init();
  });
<commit_msg>OPSMN-4842: On successful API call, clear out the container auto selection strategy.<commit_after>angular.module('os.biospecimen.cp')
  .controller('CpContainerSettingsCtrl', function($scope, $translate, cp, Container) {
    function init() {
      $scope.contSettingsCtx = {}
      setViewCtx();
    }

    function setViewCtx() {
      angular.extend($scope.contSettingsCtx, {view: 'view_settings', cp: cp});
      setStrategy();
    }

    function setStrategy() {
      $translate('cp.container.title').then(
        function() {
          if (cp.containerSelectionStrategy) {
            var alloc = cp.containerSelectionStrategy.replace(/-/g, '_');
            $scope.contSettingsCtx.strategy = $translate.instant('container.alloc_strategies.' + alloc);
          } else {
            $scope.contSettingsCtx.strategy = $translate.instant('common.not_specified');
          }
        }
      );
    }

    function setEditCtx() {
      var ctx = {view: 'edit_settings', cp: angular.copy(cp)};
      angular.extend($scope.contSettingsCtx, ctx);
      loadStrategies();
    }

    function loadStrategies() {
      if ($scope.contSettingsCtx.allocStrategies) {
        return;
      }

      Container.getAutoAllocStrategies().then(
        function(strategies) {
          $scope.contSettingsCtx.allocStrategies = strategies;
        }
      );
    }

    $scope.showEditForm = function() {
      setEditCtx();
    }

    $scope.revertEdit = function() {
      setViewCtx();
    }

    $scope.clearStoreAliquots = function() {
      if (!$scope.contSettingsCtx.cp.containerSelectionStrategy) {
        $scope.contSettingsCtx.cp.aliquotsInSameContainer = false;
      }
    }

    $scope.save = function() {
      delete $scope.contSettingsCtx.cp.repositoryNames;
      delete $scope.contSettingsCtx.cp.extensionDetail;
      delete $scope.contSettingsCtx.cp.catalogSetting;

      $scope.contSettingsCtx.cp.$saveOrUpdate().then(
        function(savedCp) {
          cp.containerSelectionStrategy = $scope.cp.containerSelectionStrategy = savedCp.containerSelectionStrategy;
          cp.aliquotsInSameContainer = $scope.aliquotsInSameContainer = savedCp.aliquotsInSameContainer;
          angular.extend(cp, angular.extend($scope.cp, savedCp));
          setViewCtx();
        }
      );
    }

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


<commit_before>pagesManagerFactory.$inject = ['postsService', '$q', 'lodash', 'moment', 'instagramService'];

export default function pagesManagerFactory(postsService, $q, _, moment, instagramService) {

    function PagesManager (blog_id, status, max_results, sort, sticky, highlight, noSynd) {
        var SORTS = {
            'editorial': {order: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'updated_first': {_updated: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'newest_first': {_created: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'oldest_first': {_created: {order: 'asc', missing: '_last', unmapped_type: 'long'}}
        };
        var self = this;

        /**
         * Represent a page of posts
         * @param {array} [posts=[]] - a list of post to initialize the page
         */
        function Page(posts) {
            return {
                posts: posts || [],
                addPost: function(post) {this.posts.push(post);}
            };
        }

        /**
         * Retrieve the given page of post from the api
         * @param {integer} page - The page index to retrieve (start from 1)
         * @param {integer} [max_results=self.maxResults] - The maximum number of results to retrieve
         * @returns {promise}
         */
        function retrievePage(page, max_results) {

            var options = {status: self.status, authors: self.authors}
            //only care about the sticky status if post if open otherwise show them all together
            //@TODO refactor when refactoring the page manager
            if (self.status === 'open') {
                options.sticky = sticky
            }

            if (noSynd)
                options.noSyndication = true;

            //only care about the highlight status if it is set to true
            if (self.highlight) {
                options.highlight = self.highlight;
            }
            return postsService.getPosts(self.blogId, options ,max_results || self.maxResults, page)
            .then(function(data) {
                // update posts meta data (used to know the total number of posts and pages)
                self.meta = data._meta;
                return data;
            });
        }
        /**
         * Filter the posts in timeline by their highlight attribute
         * @param {boolean} highlight - The value of the field (true or false)
         * @returns {promise}
         */
        function changeHighlight(highlight) {
            self.highlight = highlight;
            self.pages = [];
            return fetchNewPage();
        }
        /**
         * Change the order in the future posts request, remove exising post and load a new page
         * @param {string} sort_name - The name of the new order (see self.SORTS)
         * @returns {promise}
         */
        function changeOrder(sort_name) {
            self.sort = sort_name;
            self.pages = [];
            return fetchNewPage();
        }

        /**
         * Filter by author ids.
         * @param {array} authors - The list of ids to filter with
         * @returns {promise}
         */
        function setAuthors(authors) {
            self.authors = authors;
            self.pages = [];
            return fetchNewPage();
        }

        /**
         * Fetch a new page of posts and add it to the Pages Manager.
         * @returns {promise}
         */
        function fetchNewPage() {
            var promise = $q.when();
            // for the first time, retrieve the updates just to know the latest update date
            if (self.pages.length === 0) {
                promise = self.retrieveUpdate().then(function(updates) {
                    updateLatestDates(updates._items);
                });
            }
            return promise.then(function() {
                return loadPage(self.pages.length + 1);
            });
        }

        /**
         * Retrieve all the updates since the latest updated date
         * @param {boolean} [should_apply_updates=false] - If true, will apply the updates into the posts list
         * @returns {promise}
         */
        function retrieveUpdate(should_apply_updates) {
            should_apply_updates = should_apply_updates === true;
            var date = self.latestUpdatedDate ? self.latestUpdatedDate.utc().format() : undefined;
            var page = 1;
            return postsService.getPosts(self.blogId, {updatedAfter: date, excludeDeleted: false}, undefined, page)
            .then(function(updates) {
                var meta = updates._meta;
                // if
                // - there is no other page
                // - or if we don't give a latest update date (b/c we look after the meta or the latest date)
                // = then we return the first page of result
                if (meta.total <= meta.max_results * meta.page || !angular.isDefined(date)) {
                    return updates;
                // Otherwise we ask page after page and concatenate them in the response
                }

                var promises = [];

                for (var i = meta.page + 1; i <= Math.floor(meta.total / meta.max_results) + 1; i++) {
                    page = i;
                    promises.push(postsService.getPosts(
                        self.blogId,
                        {updatedAfter: date, excludeDeleted: false},
                        undefined,
                        page
                    ));
                }
                return $q.all(promises).then(function(updates_pages) {
                    return angular.extend({}, updates_pages[0], {
                        _items: [].concat.apply([], updates_pages.map(function(update) {return update._items;})),
                        _meta: angular.extend(meta, {max_results: meta.max_results * updates_pages.length})
                    });
                });
            })
            // Apply the update if needed
            .then(function(updates) {
                if (should_apply_updates) {
                    applyUpdates(updates._items);
                }
                return updates;
            });

        }

        /**
         * Apply the given updates to the posts list
         * @param {array} updates - List of updated posts
         */
        function applyUpdates(updates) {
            updates.forEach(function(post) {
                var existing_post_indexes = getPostPageIndexes(post);
                if (angular.isDefined(existing_post_indexes)) {
                    // post already in the list
                    if (post.deleted) {
                        // post deleted
                        removePost(post);
                        // post updated
                    } if (post.post_status !== self.status
                    || (self.status === 'open' && post.sticky !== sticky)
                    || (self.highlight && !post.lb_highlight)) {
                       removePost(post);
                    } else {
                          // update
                        self.pages[existing_post_indexes[0]].posts[existing_post_indexes[1]] = post;
                        createPagesWithPosts(self.allPosts(), true);
                    }
                    // post doesn't exist in the list
                } else if (!post.deleted
                && post.post_status === self.status
                && (self.status !== 'open' || post.sticky === sticky)) {
                    addPost(post);
                }
            });
            // update date
            updateLatestDates(updates);
        }

        /**
         * Update the latest update date by using the given posts
         * @param {array} posts - List of posts
         */
        function updateLatestDates(posts) {
            var date;
            posts.forEach(function(post) {
                date = moment(post._updated);
                if (angular.isDefined(self.latestUpdatedDate)) {
                    if (self.latestUpdatedDate.diff(date) < 0) {
                        self.latestUpdatedDate = date;
                    }
                } else {
                    self.latestUpdatedDate = date;
                }
            });
        }

        /**
         * Recreate the pages from the given posts
         * @param {array} [posts=self.allPosts()] - List of posts
         * @param {boolean} resetPages - Clear the array of pages or not
         */
        function createPagesWithPosts(posts, resetPages) {
            posts = posts || self.allPosts();
            if (resetPages) {
                self.pages = [];
            }
            // respect the order
            var sort_by = Object.keys(SORTS[self.sort])[0];
            var order_by = SORTS[self.sort][sort_by].order;
            posts = _.sortByOrder(posts, sort_by, order_by);
            var page;
            var processInstagram = false;
            posts.forEach(function(post, index) {
                if (index % self.maxResults === 0) {
                    page = new Page();
                }
                page.addPost(post);
                if (page.posts.length === self.maxResults) {
                    addPage(page);
                    page = undefined;
                }

                processInstagram = instagramService.postHasEmbed(post.items);
            });
            if (angular.isDefined(page)) {
                addPage(page);
            }
            if (processInstagram) {
                instagramService.processEmbeds();
            };
        }

        /**
         * Load the content of the given page
         * @param {interger} page - index of the desired page
         * @returns {promise}
         */
        function loadPage(page) {
            page = page || self.pages.length;
            return retrievePage(page).then(function(posts) {
                createPagesWithPosts(posts._items, false);
                return posts;

            });
        }

        /**
         * Returns the page index and the post index of the given post in the local pages
         * @param {Post} post_to_find - post to find in the pages
         * @returns {array|undefined} - [page_index, post_index]
         */
        function getPostPageIndexes(post_to_find){
            var page;
            for (var page_index = 0; page_index < self.pages.length; page_index++) {
                page = self.pages[page_index];
                for (var post_index = 0; post_index < page.posts.length; post_index++) {
                    if (page.posts[post_index]._id === post_to_find._id) {
                        return [page_index, post_index];
                    }
                }
            }
        }

        /**
         * Add a post or a list of posts to the local pages
         * @param {Post|array<Post>} posts - posts to be added to the pages
         */
        function addPost(posts) {
            var all_posts = self.allPosts();
            if (!angular.isArray(posts)) {
                posts = [posts];
            }
            // for every post, check if exist before or add it
            posts.forEach(function(post) {
                if (!angular.isDefined(getPostPageIndexes(post))) {
                    all_posts.push(post);
                }
            });
            // and recreate pages
            createPagesWithPosts(all_posts, true);
            // update date
            updateLatestDates(all_posts);
        }

        /**
         * Remove a post in the local pages
         * @param {Post} post_to_remove - posts to be removed from the pages
         */
        function removePost(post_to_remove) {
            var indexes = getPostPageIndexes(post_to_remove);
            if (angular.isDefined(post_to_remove)) {
                var page_index = indexes[0];
                var post_index = indexes[1];
                self.pages[page_index].posts.splice(post_index, 1);
                createPagesWithPosts(self.allPosts(), true);
            }
        }

        /**
         * Add the given page to the Page Manager
         * @param {Page} page - a page instance
         */
        function addPage(page) {
            self.pages.push(page);
        }

        /**
         * Returns the number of posts in the local pages
         * @returns {integer}
         */
        function count() {
            return self.pages.reduce(function(previous_value, current_value) {
                return previous_value + current_value.posts.length;
            }, 0);
        }

        angular.extend(self, {
            /**
             * List of page instances
             */
            pages: [],
            /**
             * Represent the meta data of posts (total number for instance)
             */
            meta: {},
            /**
             * Set the initial order (see self.SORTS)
             */
            sort: sort || 'editorial',
            blogId: blog_id,
            status: status,
            /**
             * Filter by post's highlight field
             */
            highlight: highlight,
            sticky: sticky,
            changeHighlight: changeHighlight,
            /**
             * Change the order in the future posts request, remove exising post and load a new page
             */
            changeOrder: changeOrder,
            /**
             * Initial authors filter
             */
            authors: [],
            /**
             * Filter by author ids
             */
            setAuthors: setAuthors,
            /**
             * Number of results per page
             */
            maxResults: max_results,
            /**
             *
             * Remove a post from the page
             */
            removePost: removePost,
            /**
             * Latest updated date. Used for retrieving updates since this date.
             */
            latestUpdatedDate: undefined,
            /**
             * Fetch a new page of posts
             */
            fetchNewPage: fetchNewPage,
            /**
             * Return the latest available updates
             */
            retrieveUpdate: retrieveUpdate,
            /**
             * Return all the posts from the local pages
             */
            allPosts: function () {
                var posts = self.pages.map(function(page) {
                    return page.posts.map(function(post) {
                        return post;
                    });
                });
                // flatten array
                var merged = [];
                return merged.concat.apply(merged, posts);
            },
            /**
             * Returns the number of posts in the local pages
             */
            count: count
        });
    }

    // return the Pages Manager constructor
    return PagesManager;
}
<commit_msg>Removed funky shit<commit_after>pagesManagerFactory.$inject = ['postsService', '$q', 'lodash', 'moment', 'instagramService'];

export default function pagesManagerFactory(postsService, $q, _, moment, instagramService) {

    function PagesManager (blog_id, status, max_results, sort, sticky, highlight, noSynd) {
        var SORTS = {
            'editorial': {order: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'updated_first': {_updated: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'newest_first': {_created: {order: 'desc', missing: '_last', unmapped_type: 'long'}},
            'oldest_first': {_created: {order: 'asc', missing: '_last', unmapped_type: 'long'}}
        };
        var self = this;

        /**
         * Represent a page of posts
         * @param {array} [posts=[]] - a list of post to initialize the page
         */
        function Page(posts) {
            return {
                posts: posts || [],
                addPost: function(post) {this.posts.push(post);}
            };
        }

        /**
         * Retrieve the given page of post from the api
         * @param {integer} page - The page index to retrieve (start from 1)
         * @param {integer} [max_results=self.maxResults] - The maximum number of results to retrieve
         * @returns {promise}
         */
        function retrievePage(page, max_results) {

            var options = {status: self.status, authors: self.authors}
            //only care about the sticky status if post if open otherwise show them all together
            //@TODO refactor when refactoring the page manager
            if (self.status === 'open') {
                options.sticky = sticky
            }

            if (noSynd)
                options.noSyndication = true;

            //only care about the highlight status if it is set to true
            if (self.highlight) {
                options.highlight = self.highlight;
            }
            return postsService.getPosts(self.blogId, options ,max_results || self.maxResults, page)
            .then(function(data) {
                // update posts meta data (used to know the total number of posts and pages)
                self.meta = data._meta;
                return data;
            });
        }
        /**
         * Filter the posts in timeline by their highlight attribute
         * @param {boolean} highlight - The value of the field (true or false)
         * @returns {promise}
         */
        function changeHighlight(highlight) {
            self.highlight = highlight;
            self.pages = [];
            return fetchNewPage();
        }
        /**
         * Change the order in the future posts request, remove exising post and load a new page
         * @param {string} sort_name - The name of the new order (see self.SORTS)
         * @returns {promise}
         */
        function changeOrder(sort_name) {
            self.sort = sort_name;
            self.pages = [];
            return fetchNewPage();
        }

        /**
         * Filter by author ids.
         * @param {array} authors - The list of ids to filter with
         * @returns {promise}
         */
        function setAuthors(authors) {
            self.authors = authors;
            self.pages = [];
            return fetchNewPage();
        }

        /**
         * Fetch a new page of posts and add it to the Pages Manager.
         * @returns {promise}
         */
        function fetchNewPage() {
            var promise = $q.when();
            // for the first time, retrieve the updates just to know the latest update date
            if (self.pages.length === 0) {
                promise = self.retrieveUpdate().then(function(updates) {
                    updateLatestDates(updates._items);
                });
            }
            return promise.then(function() {
                return loadPage(self.pages.length + 1);
            });
        }

        /**
         * Retrieve all the updates since the latest updated date
         * @param {boolean} [should_apply_updates=false] - If true, will apply the updates into the posts list
         * @returns {promise}
         */
        function retrieveUpdate(should_apply_updates) {
            should_apply_updates = should_apply_updates === true;
            var date = self.latestUpdatedDate ? self.latestUpdatedDate.utc().format() : undefined;
            var page = 1;
            return postsService.getPosts(self.blogId, {updatedAfter: date, excludeDeleted: false}, undefined, page)
            .then(function(updates) {
                var meta = updates._meta;
                // if
                // - there is no other page
                // - or if we don't give a latest update date (b/c we look after the meta or the latest date)
                // = then we return the first page of result
                if (meta.total <= meta.max_results * meta.page || !angular.isDefined(date)) {
                    return updates;
                // Otherwise we ask page after page and concatenate them in the response
                }

                var promises = [];

                for (var i = meta.page + 1; i <= Math.floor(meta.total / meta.max_results) + 1; i++) {
                    page = i;
                    promises.push(postsService.getPosts(
                        self.blogId,
                        {updatedAfter: date, excludeDeleted: false},
                        undefined,
                        page
                    ));
                }
                return $q.all(promises).then(function(updates_pages) {
                    return angular.extend({}, updates_pages[0], {
                        _items: [].concat.apply([], updates_pages.map(function(update) {return update._items;})),
                        _meta: angular.extend(meta, {max_results: meta.max_results * updates_pages.length})
                    });
                });
            })
            // Apply the update if needed
            .then(function(updates) {
                if (should_apply_updates) {
                    applyUpdates(updates._items);
                }
                return updates;
            });

        }

        /**
         * Apply the given updates to the posts list
         * @param {array} updates - List of updated posts
         */
        function applyUpdates(updates) {
            updates.forEach(function(post) {
                var existing_post_indexes = getPostPageIndexes(post);
                if (angular.isDefined(existing_post_indexes)) {
                    // post already in the list
                    if (post.deleted) {
                        // post deleted
                        removePost(post);
                        // post updated
                    } if (post.post_status !== self.status
                    || (self.status === 'open' && post.sticky !== sticky)
                    || (self.highlight && !post.lb_highlight)) {
                       removePost(post);
                    } else {
                        createPagesWithPosts(self.allPosts(), true);
                    }
                    // post doesn't exist in the list
                } else if (!post.deleted
                && post.post_status === self.status
                && (self.status !== 'open' || post.sticky === sticky)) {
                    addPost(post);
                }
            });
            // update date
            updateLatestDates(updates);
        }

        /**
         * Update the latest update date by using the given posts
         * @param {array} posts - List of posts
         */
        function updateLatestDates(posts) {
            var date;
            posts.forEach(function(post) {
                date = moment(post._updated);
                if (angular.isDefined(self.latestUpdatedDate)) {
                    if (self.latestUpdatedDate.diff(date) < 0) {
                        self.latestUpdatedDate = date;
                    }
                } else {
                    self.latestUpdatedDate = date;
                }
            });
        }

        /**
         * Recreate the pages from the given posts
         * @param {array} [posts=self.allPosts()] - List of posts
         * @param {boolean} resetPages - Clear the array of pages or not
         */
        function createPagesWithPosts(posts, resetPages) {
            posts = posts || self.allPosts();
            if (resetPages) {
                self.pages = [];
            }
            // respect the order
            var sort_by = Object.keys(SORTS[self.sort])[0];
            var order_by = SORTS[self.sort][sort_by].order;
            posts = _.sortByOrder(posts, sort_by, order_by);
            var page;
            var processInstagram = false;
            posts.forEach(function(post, index) {
                if (index % self.maxResults === 0) {
                    page = new Page();
                }
                page.addPost(post);
                if (page.posts.length === self.maxResults) {
                    addPage(page);
                    page = undefined;
                }

                processInstagram = instagramService.postHasEmbed(post.items);
            });
            if (angular.isDefined(page)) {
                addPage(page);
            }
            if (processInstagram) {
                instagramService.processEmbeds();
            };
        }

        /**
         * Load the content of the given page
         * @param {interger} page - index of the desired page
         * @returns {promise}
         */
        function loadPage(page) {
            page = page || self.pages.length;
            return retrievePage(page).then(function(posts) {
                createPagesWithPosts(posts._items, false);
                return posts;

            });
        }

        /**
         * Returns the page index and the post index of the given post in the local pages
         * @param {Post} post_to_find - post to find in the pages
         * @returns {array|undefined} - [page_index, post_index]
         */
        function getPostPageIndexes(post_to_find){
            var page;
            for (var page_index = 0; page_index < self.pages.length; page_index++) {
                page = self.pages[page_index];
                for (var post_index = 0; post_index < page.posts.length; post_index++) {
                    if (page.posts[post_index]._id === post_to_find._id) {
                        return [page_index, post_index];
                    }
                }
            }
        }

        /**
         * Add a post or a list of posts to the local pages
         * @param {Post|array<Post>} posts - posts to be added to the pages
         */
        function addPost(posts) {
            var all_posts = self.allPosts();
            if (!angular.isArray(posts)) {
                posts = [posts];
            }
            // for every post, check if exist before or add it
            posts.forEach(function(post) {
                if (!angular.isDefined(getPostPageIndexes(post))) {
                    all_posts.push(post);
                }
            });
            // and recreate pages
            createPagesWithPosts(all_posts, true);
            // update date
            updateLatestDates(all_posts);
        }

        /**
         * Remove a post in the local pages
         * @param {Post} post_to_remove - posts to be removed from the pages
         */
        function removePost(post_to_remove) {
            var indexes = getPostPageIndexes(post_to_remove);
            if (angular.isDefined(post_to_remove)) {
                var page_index = indexes[0];
                var post_index = indexes[1];
                self.pages[page_index].posts.splice(post_index, 1);

                createPagesWithPosts(self.allPosts(), true);
            }
        }

        /**
         * Add the given page to the Page Manager
         * @param {Page} page - a page instance
         */
        function addPage(page) {
            self.pages.push(page);
        }

        /**
         * Returns the number of posts in the local pages
         * @returns {integer}
         */
        function count() {
            return self.pages.reduce(function(previous_value, current_value) {
                return previous_value + current_value.posts.length;
            }, 0);
        }

        angular.extend(self, {
            /**
             * List of page instances
             */
            pages: [],
            /**
             * Represent the meta data of posts (total number for instance)
             */
            meta: {},
            /**
             * Set the initial order (see self.SORTS)
             */
            sort: sort || 'editorial',
            blogId: blog_id,
            status: status,
            /**
             * Filter by post's highlight field
             */
            highlight: highlight,
            sticky: sticky,
            changeHighlight: changeHighlight,
            /**
             * Change the order in the future posts request, remove exising post and load a new page
             */
            changeOrder: changeOrder,
            /**
             * Initial authors filter
             */
            authors: [],
            /**
             * Filter by author ids
             */
            setAuthors: setAuthors,
            /**
             * Number of results per page
             */
            maxResults: max_results,
            /**
             *
             * Remove a post from the page
             */
            removePost: removePost,
            /**
             * Latest updated date. Used for retrieving updates since this date.
             */
            latestUpdatedDate: undefined,
            /**
             * Fetch a new page of posts
             */
            fetchNewPage: fetchNewPage,
            /**
             * Return the latest available updates
             */
            retrieveUpdate: retrieveUpdate,
            /**
             * Return all the posts from the local pages
             */
            allPosts: function () {
                var posts = self.pages.map(function(page) {
                    return page.posts.map(function(post) {
                        return post;
                    });
                });
                // flatten array
                var merged = [];
                return merged.concat.apply(merged, posts);
            },
            /**
             * Returns the number of posts in the local pages
             */
            count: count
        });
    }

    // return the Pages Manager constructor
    return PagesManager;
}
<|endoftext|>