<commit_before>/**
 * @overview ccm component for real time  Forum
 * @author Tea Kless <tea.kless@web.de>, 2017
 * @license The MIT License (MIT)
 */

( function () {

  var ccm_version = '9.0.0';
  var ccm_url     = 'https://akless.github.io/ccm/version/ccm-9.0.0.min.js';

  var component_name = 'forum';
  var component_obj  = {
    name: component_name,

    config: {
      templates: {
        "main": {
          "class": "container",
          "inner": [
            {
              "id": "questions-view",
              "inner":
                [
                  {
                    "id": "questions-list"
                  },
                  {
                    "tag": "hr"
                  },
                  {
                    "tag": "form",
                    "onsubmit": "%submit%",
                    "class": "form-horizontal",
                    "inner": [
                      {
                        "class": "form-group",
                        "id": "new-title",
                        "inner":
                          [
                            {
                              "tag": "label",
                              "class": "control-label col-sm-1",
                              "for": "%for%",
                              "inner": "%inner%"
                            },
                            {
                              "class": "col-sm-11",
                              "inner": {
                                "tag": "input",
                                "type": "%type%",
                                "class": "form-control",
                                "id":"%id%",
                                "placeholder": "%value%"
                              }
                            }
                          ]
                      },
                      {
                        "id": "editor",
                        "class": "form-group",
                        "inner": [
                          {
                            "tag":  "input",
                            "name": "new",
                            "type": "hidden"
                          },
                          {
                            "id": "editor-container"
                          }
                        ]
                      },
                      {
                        "id": "button",
                        "class": "form-group",
                        "inner": {
                          "class": "row",
                          "inner": {
                            "tag": "button",
                            "type": "submit",
                            "class": "btn btn-primary",
                            "inner": "Post new Question"
                          }
                        }
                      }
                    ]

                  }
                ]
            },
            {
              "id": "answers-view"
            }
          ]
        },

        "answers": {
          "inner": [
            {
              "tag": "ul",
              "class": "nav nav-tabs",
              "inner": [
                {
                  "tag": "li",
                  "class": "active",
                  "onclick": "%render_questions%",
                  "inner": { "tag": "a", "inner": "Questions" }
                }
              ]
            },
            {
              "id": "answers"
            }

          ]
        },

        "question": {
          "class": "question row",
          "inner": [
            {
              "class": "voting-overview col-md-2 text-center",
              "inner":
                [
                  {
                    "class": "row",
                    "inner": [
                      {
                        "class": "vote-sum col-md-6 col-xs-6",
                        "inner": "%votes%"
                      },
                      {
                        "class": "answer_sum col-md-6 col-xs-6",
                        "inner": "%answers%"
                      }
                    ]
                  },
                  {
                    "class": "row",
                    "inner": [
                      {
                        "class": "vote-label col-md-6 col-xs-6",
                        "inner": "votes"
                      },
                      {
                        "class": "vote-label col-md-6 col-xs-6",
                        "inner": "answers"
                      }
                    ]
                  }
                ]
            },
            {
              "class": "question-summery col-md-10",
              "onclick": "%render_answers%",
              "inner": {
                "tag": "blockquote",
                "inner":[
                  {
                    "tag": "p",
                    "class": "question_title",
                    "inner": "%title%"
                  },
                  {
                    "tag": "footer",
                    "class": "question_footer",
                    "inner": "%signatur%"
                  }
                ]
              }
            }
          ]
        }
      },

      data: {
        store: [ 'ccm.store', '../forum/datastore.json' ],
        key: "demo"
      },
      user:  [ 'ccm.instance', 'https://akless.github.io/ccm-components/user/ccm.user.min.js' ],
      style: [ 'ccm.load', '../forum/style.css' ],
      editor: [ 'ccm.component', 'https://tkless.github.io/ccm-components/editor/ccm.editor.js',
        { 'settings.modules.toolbar': [
            [{ 'size': ['small', false, 'large', 'huge'] }],  // custom dropdown
            ['bold', 'italic', 'underline'],        // toggled buttons
            ['blockquote', 'code-block'],

            [{ 'header': 1 }, { 'header': 2 }],               // custom button values
            [{ 'list': 'ordered'}, { 'list': 'bullet' }],
            [{ 'script': 'sub'}, { 'script': 'super' }],      // superscript/subscript
            [{ 'indent': '-1'}, { 'indent': '+1' }],          // outdent/indent

            [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme
            [{ 'align': [] }]
        ] }
      ],
      question: [ 'ccm.component', '../question/ccm.question.js' ],
      new_question_conf: { data: { store: [ 'ccm.store' ] } },
      bootstrap: [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css', { context: 'head', url: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css' } ]
    },

    Instance: function () {
      var self = this;
      var editor;
      var question;

      this.start = function (callback) {

        self.ccm.helper.dataset(self.data.store, self.data.key, function ( dataset ) {

          self.ccm.helper.setContent( self.element, self.ccm.helper.protect( self.ccm.helper.html( self.templates.main, {
            for:   "title",
            inner: "Title",
            id:    "title",
            type:  "text",
            value: "What is your Question ?",
            submit: function ( event ) { console.log( "huhu" ); event.preventDefault(); newQuestion(); }
          } ) ) );

          renderQuestions();
          renderEditor();

          function renderQuestions() {

            dataset.questions.map( renderQuestion );

            function renderQuestion( question_conf ) {

              // start question instance
              self.question.start( question_conf , function ( question_instance ) {

                // get question data of lunched question instance
                question_instance.data.store.get( question_instance.data.key, function ( question_data ) {

                  // get voting instance of question instance
                  question_instance.voting.instance( question_data.voting, function ( voting_instance ) {
                    question = question_data;
                    self.element.querySelector( '#questions-list' ).appendChild( self.ccm.helper.html( self.templates.question, {
                      title:    question_data.title,
                      signatur: question_data.date,
                      votes:    voting_instance.getVoting(),
                      answers:  question_data.answers.length,
                      render_answers: function () {
                        self.element.querySelector( '#questions-view' ).style.display = 'none';

                        self.ccm.helper.setContent( self.element.querySelector( '#answers-view' ), self.ccm.helper.html( self.templates.answers, {
                          render_questions: function () {
                            self.element.querySelector( '#answers-view' ).innerHTML = '';
                            self.element.querySelector( '#questions-view' ).style.display = 'block';
                          }
                        } ) );

                        self.element.querySelector( '#answers' ).appendChild( question_instance.root );
                      }
                    } ) );

                  } );

                } );
              } )
            }
          }

          function renderEditor() {
            self.editor.start( { root: self.element.querySelector( '#editor-container' ) }, function ( instance ) {
              editor = instance;
            } );
          }

          function newQuestion() {
            if ( !self.user ) return;

            self.user.login( function () {
              var user = self.user.data().user;

              var question_title = self.element.querySelector( 'input[ id = title ]' ).value;

              var new_question_data = {
                "date": getDateTime(),
                "user": user,
                "title": question_title,
                "content": editor.get().root.innerHTML,
                "voting": { },
                "answers": [ ]
              } ;

              var new_question_conf = self.ccm.helper.clone( self.new_question_conf );

              self.question.instance( new_question_conf, function ( new_question_inst ) {

                new_question_inst.data.store.set( new_question_data, function ( created_qustion_data ) {
                  new_question_conf.data.key = created_qustion_data.key;
                  dataset.questions.push( new_question_conf );
                  self.data.store.set( dataset, function () { self.start(); } );
                } );
              } );

            } );

          }

          function getDateTime() {

            var today = new Date();
            var dd    = today.getDate();
            var mm    = today.getMonth();
            var yyyy  = today.getFullYear();
            var hour  = today.getHours();
            var min   = today.getMinutes();

            var monat = ["Januar", "Februar", "März", "April", "Mai", "Juni","Juli", "August", "September", "Oktober", "November", "Dezember"];

            if ( dd < 10 ) dd = '0' + dd;

            if ( hour < 10 ) hour = '0' + hour;
            if ( min  < 10 ) min  = '0' + min;

            return dd + ' ' + monat[ mm].substring(0, 3) + '. '  + yyyy + ' ' + hour + ':' + min;

          }

          if ( callback ) callback();

        } );
      };
    }
  };

  var namespace = window.ccm && ccm.components[ component_name ]; if ( namespace ) { if ( namespace.ccm_version ) ccm_version = namespace.ccm_version; if ( namespace.ccm_url ) ccm_url = namespace.ccm_url; }
  if ( !window.ccm || !ccm[ ccm_version ] ) { var tag = document.createElement( 'script' ); document.head.appendChild( tag ); tag.onload = register; tag.src = ccm_url; } else register();
  function register() { ccm[ ccm_version ].component( component_obj ); }
}() );
<commit_msg>compatibility to ccm v9.2.0<commit_after>/**
 * @overview ccm component for real time  Forum
 * @author Tea Kless <tea.kless@web.de>, 2017
 * @license The MIT License (MIT)
 */

( function () {

  var filename = "ccm.forum.js";

  var ccm_version = '9.2.0';
  var ccm_url     = 'https://akless.github.io/ccm/version/ccm-9.2.0.min.js';

  var component_name = 'forum';
  var component_obj  = {
    name: component_name,

    config: {
      templates: {
        "main": {
          "class": "container",
          "inner": [
            {
              "id": "questions-view",
              "inner":
                [
                  {
                    "id": "questions-list"
                  },
                  {
                    "tag": "hr"
                  },
                  {
                    "tag": "form",
                    "onsubmit": "%submit%",
                    "class": "form-horizontal",
                    "inner": [
                      {
                        "class": "form-group",
                        "id": "new-title",
                        "inner":
                          [
                            {
                              "tag": "label",
                              "class": "control-label col-sm-1",
                              "for": "%for%",
                              "inner": "%inner%"
                            },
                            {
                              "class": "col-sm-11",
                              "inner": {
                                "tag": "input",
                                "type": "%type%",
                                "class": "form-control",
                                "id":"%id%",
                                "placeholder": "%value%"
                              }
                            }
                          ]
                      },
                      {
                        "id": "editor",
                        "class": "form-group",
                        "inner": [
                          {
                            "tag":  "input",
                            "name": "new",
                            "type": "hidden"
                          },
                          {
                            "id": "editor-container"
                          }
                        ]
                      },
                      {
                        "id": "button",
                        "class": "form-group",
                        "inner": {
                          "class": "row",
                          "inner": {
                            "tag": "button",
                            "type": "submit",
                            "class": "btn btn-primary",
                            "inner": "Post new Question"
                          }
                        }
                      }
                    ]

                  }
                ]
            },
            {
              "id": "answers-view"
            }
          ]
        },

        "answers": {
          "inner": [
            {
              "tag": "ul",
              "class": "nav nav-tabs",
              "inner": [
                {
                  "tag": "li",
                  "class": "active",
                  "onclick": "%render_questions%",
                  "inner": { "tag": "a", "inner": "Questions" }
                }
              ]
            },
            {
              "id": "answers"
            }

          ]
        },

        "question": {
          "class": "question row",
          "inner": [
            {
              "class": "voting-overview col-md-2 text-center",
              "inner":
                [
                  {
                    "class": "row",
                    "inner": [
                      {
                        "class": "vote-sum col-md-6 col-xs-6",
                        "inner": "%votes%"
                      },
                      {
                        "class": "answer_sum col-md-6 col-xs-6",
                        "inner": "%answers%"
                      }
                    ]
                  },
                  {
                    "class": "row",
                    "inner": [
                      {
                        "class": "vote-label col-md-6 col-xs-6",
                        "inner": "votes"
                      },
                      {
                        "class": "vote-label col-md-6 col-xs-6",
                        "inner": "answers"
                      }
                    ]
                  }
                ]
            },
            {
              "class": "question-summery col-md-10",
              "onclick": "%render_answers%",
              "inner": {
                "tag": "blockquote",
                "inner":[
                  {
                    "tag": "p",
                    "class": "question_title",
                    "inner": "%title%"
                  },
                  {
                    "tag": "footer",
                    "class": "question_footer",
                    "inner": "%signatur%"
                  }
                ]
              }
            }
          ]
        }
      },

      data: {
        store: [ 'ccm.store', '../forum/datastore.json' ],
        key: "demo"
      },
      user:  [ 'ccm.instance', 'https://akless.github.io/ccm-components/user/ccm.user.min.js' ],
      style: [ 'ccm.load', '../forum/style.css' ],
      editor: [ 'ccm.component', 'https://tkless.github.io/ccm-components/editor/ccm.editor.js',
        { 'settings.modules.toolbar': [
            [{ 'size': ['small', false, 'large', 'huge'] }],  // custom dropdown
            ['bold', 'italic', 'underline'],        // toggled buttons
            ['blockquote', 'code-block'],

            [{ 'header': 1 }, { 'header': 2 }],               // custom button values
            [{ 'list': 'ordered'}, { 'list': 'bullet' }],
            [{ 'script': 'sub'}, { 'script': 'super' }],      // superscript/subscript
            [{ 'indent': '-1'}, { 'indent': '+1' }],          // outdent/indent

            [{ 'color': [] }, { 'background': [] }],          // dropdown with defaults from theme
            [{ 'align': [] }]
        ] }
      ],
      question: [ 'ccm.component', '../question/ccm.question.js' ],
      new_question_conf: { data: { store: [ 'ccm.store' ] } },
      bootstrap: [ 'ccm.load', 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css', { context: 'head', url: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css' } ]
    },

    Instance: function () {
      var self = this;
      var editor;
      var question;

      this.start = function (callback) {

        self.ccm.helper.dataset(self.data.store, self.data.key, function ( dataset ) {

          self.ccm.helper.setContent( self.element, self.ccm.helper.protect( self.ccm.helper.html( self.templates.main, {
            for:   "title",
            inner: "Title",
            id:    "title",
            type:  "text",
            value: "What is your Question ?",
            submit: function ( event ) { console.log( "huhu" ); event.preventDefault(); newQuestion(); }
          } ) ) );

          renderQuestions();
          renderEditor();

          function renderQuestions() {

            dataset.questions.map( renderQuestion );

            function renderQuestion( question_conf ) {

              // start question instance
              self.question.start( question_conf , function ( question_instance ) {

                // get question data of lunched question instance
                question_instance.data.store.get( question_instance.data.key, function ( question_data ) {

                  // get voting instance of question instance
                  question_instance.voting.instance( question_data.voting, function ( voting_instance ) {
                    question = question_data;
                    self.element.querySelector( '#questions-list' ).appendChild( self.ccm.helper.html( self.templates.question, {
                      title:    question_data.title,
                      signatur: question_data.date,
                      votes:    voting_instance.getVoting(),
                      answers:  question_data.answers.length,
                      render_answers: function () {
                        self.element.querySelector( '#questions-view' ).style.display = 'none';

                        self.ccm.helper.setContent( self.element.querySelector( '#answers-view' ), self.ccm.helper.html( self.templates.answers, {
                          render_questions: function () {
                            self.element.querySelector( '#answers-view' ).innerHTML = '';
                            self.element.querySelector( '#questions-view' ).style.display = 'block';
                          }
                        } ) );

                        self.element.querySelector( '#answers' ).appendChild( question_instance.root );
                      }
                    } ) );

                  } );

                } );
              } )
            }
          }

          function renderEditor() {
            self.editor.start( { root: self.element.querySelector( '#editor-container' ) }, function ( instance ) {
              editor = instance;
            } );
          }

          function newQuestion() {
            if ( !self.user ) return;

            self.user.login( function () {
              var user = self.user.data().user;

              var question_title = self.element.querySelector( 'input[ id = title ]' ).value;

              var new_question_data = {
                "date": getDateTime(),
                "user": user,
                "title": question_title,
                "content": editor.get().root.innerHTML,
                "voting": { },
                "answers": [ ]
              } ;

              var new_question_conf = self.ccm.helper.clone( self.new_question_conf );

              self.question.instance( new_question_conf, function ( new_question_inst ) {

                new_question_inst.data.store.set( new_question_data, function ( created_qustion_data ) {
                  new_question_conf.data.key = created_qustion_data.key;
                  dataset.questions.push( new_question_conf );
                  self.data.store.set( dataset, function () { self.start(); } );
                } );
              } );

            } );

          }

          function getDateTime() {

            var today = new Date();
            var dd    = today.getDate();
            var mm    = today.getMonth();
            var yyyy  = today.getFullYear();
            var hour  = today.getHours();
            var min   = today.getMinutes();

            var monat = ["Januar", "Februar", "März", "April", "Mai", "Juni","Juli", "August", "September", "Oktober", "November", "Dezember"];

            if ( dd < 10 ) dd = '0' + dd;

            if ( hour < 10 ) hour = '0' + hour;
            if ( min  < 10 ) min  = '0' + min;

            return dd + ' ' + monat[ mm].substring(0, 3) + '. '  + yyyy + ' ' + hour + ':' + min;

          }

          if ( callback ) callback();

        } );
      };
    }
  };

  if ( window.ccm && window.ccm.files ) window.ccm.files[ filename ] = component_obj;
  var namespace = window.ccm && ccm.components[ component_name ]; if ( namespace ) { if ( namespace.ccm_version ) ccm_version = namespace.ccm_version; if ( namespace.ccm_url ) ccm_url = namespace.ccm_url; }
  if ( !window.ccm || !ccm[ ccm_version ] ) { var tag = document.createElement( 'script' ); document.head.appendChild( tag ); tag.onload = register; tag.src = ccm_url; } else register();
  function register() { ccm[ ccm_version ].component( component_obj ); }
}() );
<|endoftext|>


<commit_before>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
 * Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

var util = require("util");
var http = require('http');
var https = require('https');
var xml2js = require('xml2js');
var urlParser = require("url");
var log4js = require("yahoo-arrow").log4js;
var Controller = require("yahoo-arrow").controller;

function WebServiceController(testConfig,args,driver) {
    Controller.call(this, testConfig,args,driver);

    this.logger = log4js.getLogger("WebServiceController");
}

/*
 * All custom controllers MUST implement the Controller interface
 */
util.inherits(WebServiceController, Controller);

/**
 * In the execute method you can get a handle to the parameters in your descriptor
 * file by using this.testParams
 *
 */
WebServiceController.prototype.execute = function(callback) {
    var self = this;

    //Get the various parameters needed from the Test Descriptor file
    var url = self.testParams.url;
    if (!url) {
        callback("url has to be defined for webservice-controller");
        return;
    }

    // parse url
    var parts = urlParser.parse(url);

    var http_opts = {
        host: parts.hostname,
        port: parts.port || '80',
        path: parts.path,
        method: self.testParams.method || 'GET',
        headers: { // seems that user-agent is MUST to query YQL
            'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31"
        }
    }

    // http or https?
    var http_protocol;
    if (parts.protocol.indexOf("https") > -1) {
        http_protocol = https;
    } else {
        http_protocol = http;
    }

    var req = http_protocol.request(http_opts, function(res) {
        var content_type = res.headers['content-type'];
        self.logger.debug("Content type: " + content_type);
        res.setEncoding('utf8');

        var shared = {};
        shared.statusCode = res.statusCode;
        shared.headers = res.headers;

        var data = '';
        res.on('data', function (chunk) {
            data += chunk;
        });

        res.on('end', function () {
            if (content_type.indexOf('xml') > -1) { // content type is xml
                var xmlParser = new xml2js.Parser();
                xmlParser.parseString(data, function (err, result) {
                    self.logger.debug("Response data:" + JSON.stringify(result));
                    shared.data = result;
                    self.testParams.shared = shared;
                    callback();
                });
            } else if (content_type.indexOf('json') > -1) { // content type is json
                var result = JSON.parse(data);
                self.logger.debug("Response data:" + JSON.stringify(result));
                shared.data = result;
                self.testParams.shared = shared;
                callback();
            } else {
                callback('Only json or xml content type is supported');
            }
        });

        res.on('error', function (e) {
            callback('Got error: ' + e.message);
        });
    });

    req.on('error', function(e) {
        callback('Problem with request: ' + e.message);
    });

    req.end();
}

module.exports = WebServiceController;
<commit_msg>return status code and headers even if response data format is not supoort and got error on response<commit_after>/*jslint forin:true sub:true anon:true, sloppy:true, stupid:true nomen:true, node:true continue:true*/

/*
 * Copyright (c) 2012, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

var util = require("util");
var http = require('http');
var https = require('https');
var xml2js = require('xml2js');
var urlParser = require("url");
var log4js = require("yahoo-arrow").log4js;
var Controller = require("yahoo-arrow").controller;

function WebServiceController(testConfig,args,driver) {
    Controller.call(this, testConfig,args,driver);

    this.logger = log4js.getLogger("WebServiceController");
}

/*
 * All custom controllers MUST implement the Controller interface
 */
util.inherits(WebServiceController, Controller);

/**
 * In the execute method you can get a handle to the parameters in your descriptor
 * file by using this.testParams
 *
 */
WebServiceController.prototype.execute = function(callback) {
    var self = this;

    //Get the various parameters needed from the Test Descriptor file
    var url = self.testParams.url;
    if (!url) {
        callback("url has to be defined for webservice-controller");
        return;
    }

    // parse url
    var parts = urlParser.parse(url);

    var http_opts = {
        host: parts.hostname,
        port: parts.port || '80',
        path: parts.path,
        method: self.testParams.method || 'GET',
        headers: { // seems that user-agent is MUST to query YQL
            'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.65 Safari/537.31"
        }
    }

    // http or https?
    var http_protocol;
    if (parts.protocol.indexOf("https") > -1) {
        http_protocol = https;
    } else {
        http_protocol = http;
    }

    var req = http_protocol.request(http_opts, function(res) {
        var content_type = res.headers['content-type'];
        self.logger.debug("Content type: " + content_type);
        res.setEncoding('utf8');

        var shared = {};
        shared.statusCode = res.statusCode;
        shared.headers = res.headers;

        var data = '';
        res.on('data', function (chunk) {
            data += chunk;
        });

        res.on('end', function () {
            if (content_type.indexOf('xml') > -1) { // content type is xml
                var xmlParser = new xml2js.Parser();
                xmlParser.parseString(data, function (err, result) {
                    self.logger.debug("Response data:" + JSON.stringify(result));
                    shared.data = result;
                    self.testParams.shared = shared;
                    callback();
                });
            } else if (content_type.indexOf('json') > -1) { // content type is json
                var result = JSON.parse(data);
                self.logger.debug("Response data:" + JSON.stringify(result));
                shared.data = result;
                self.testParams.shared = shared;
                callback();
            } else {
                self.testParams.shared = shared;
                callback('Only json or xml content type is supported');
            }
        });

        res.on('error', function (e) {
            self.testParams.shared = shared;
            callback('Got error: ' + e.message);
        });
    });

    req.on('error', function(e) {
        callback('Problem with request: ' + e.message);
    });

    req.end();
}

module.exports = WebServiceController;
<|endoftext|>


<commit_before>/* eslint-disable no-jquery/no-global-selector */
mw.echo = mw.echo || {};
mw.echo.config = mw.echo.config || {};
// Set default max prioritized action links per item
mw.echo.config.maxPrioritizedActions = 2;

/**
 * Initialise desktop Echo experience
 */
function initDesktop() {
	'use strict';

	// Remove ?markasread=XYZ from the URL
	var uri = new mw.Uri();
	if ( uri.query.markasread !== undefined ) {
		delete uri.query.markasread;
		delete uri.query.markasreadwiki;
		window.history.replaceState( null, document.title, uri );
	}

	// Activate ooui
	$( function () {
		var selectedWidget,
			echoApi,
			messageController,
			alertController,
			messageModelManager,
			alertModelManager,
			unreadMessageCounter,
			unreadAlertCounter,
			maxNotificationCount = require( './config.json' ).EchoMaxNotificationCount,
			pollingRate = require( './config.json' ).EchoPollForUpdates,
			documentTitle = document.title,
			$existingAlertLink = $( '#pt-notifications-alert a' ),
			$existingMessageLink = $( '#pt-notifications-notice a' ),
			numAlerts = $existingAlertLink.attr( 'data-counter-num' ),
			numMessages = $existingMessageLink.attr( 'data-counter-num' ),
			badgeLabelAlerts = $existingAlertLink.attr( 'data-counter-text' ),
			badgeLabelMessages = $existingMessageLink.attr( 'data-counter-text' ),
			// eslint-disable-next-line no-jquery/no-class-state
			hasUnseenAlerts = $existingAlertLink.hasClass( 'mw-echo-unseen-notifications' ),
			// eslint-disable-next-line no-jquery/no-class-state
			hasUnseenMessages = $existingMessageLink.hasClass( 'mw-echo-unseen-notifications' ),
			// latestMessageNotifTime is the time of most recent notification that came when we called showNotificationSnippet last
			// the function showNotificationSnippet returns the time of the latest notification and latestMessageNotifTime is updated
			latestMessageNotifTime = new Date(),
			latestAlertNotifTime = new Date(),
			alertCount = parseInt( numAlerts ),
			messageCount = parseInt( numMessages ),
			loadingPromise = null,
			// Store links
			links = {
				notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
				preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
					'#mw-prefsection-echo'
			};

		function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
			var totalCount = totalAlertCount + totalMessageCount,
				convertedTotalCount,
				newTitle = documentTitle;

			if ( totalCount > 0 ) {
				convertedTotalCount = totalCount <= maxNotificationCount ? totalCount : maxNotificationCount + 1;
				convertedTotalCount = mw.msg( 'echo-badge-count', mw.language.convertNumber( convertedTotalCount ) );
				newTitle = mw.msg( 'parentheses', convertedTotalCount ) + ' ' + documentTitle;
			}
			document.title = newTitle;
		}

		/**
		 * Show notification snippet via mw.notify of notifications which came after highestNotifTime.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {Date} highestNotifTime Timestamp of latest notification the last time function was called
		 * @return {Date} Timestamp of latest notification
		 */
		function showNotificationSnippet( modelManager, highestNotifTime ) {
			var timestampAsDate,
				highestTime = new Date();
			highestTime = highestNotifTime;
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				timestampAsDate = new Date( notificationItem.timestamp );
				if ( timestampAsDate > highestNotifTime ) {
					if ( timestampAsDate > highestTime ) {
						highestTime = timestampAsDate;
					}
					if ( !notificationItem.seen ) {
						mw.notify( $.parseHTML( notificationItem.content.header ), { title: mw.msg( 'echo-displaysnippet-title' ) } );
					}
				}
			}
			);
			return highestTime;
		}

		/**
		 * Change the seen state of badges if there are any unseen notifications.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {mw.echo.ui.NotificationBadgeWidget} badgeWidget
		 */
		function updateBadgeState( modelManager, badgeWidget ) {
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				if ( !notificationItem.isSeen() ) {
					badgeWidget.updateBadgeSeenState( true );
				}
			} );
		}

		function isLivePollingFeatureEnabledOnWiki() {
			return pollingRate !== 0;
		}

		/**
		 * User has opted in to preference to show notification snippets and update document title with unread count.
		 *
		 * Only useful when isLivePollingFeatureEnabledOnWiki() returns true.
		 *
		 * @return {boolean} User preference
		 */
		function userHasOptedInToLiveNotifications() {
			return mw.user.options.get( 'echo-show-poll-updates' ) === '1';
		}

		// Change document title on initialization only when polling rate feature flag is non-zero.
		if ( isLivePollingFeatureEnabledOnWiki() && userHasOptedInToLiveNotifications() ) {
			updateDocumentTitleWithNotificationCount( alertCount, messageCount );
		}

		function loadEcho() {
			if ( loadingPromise !== null ) {
				return loadingPromise;
			}
			// This part executes only once, either when header icons are clicked or after completion of 60secs whichever occur first.
			echoApi = new mw.echo.api.EchoApi();

			loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {

				// Overlay
				mw.echo.ui.$overlay.appendTo( document.body );

				unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
				alertModelManager = new mw.echo.dm.ModelManager( unreadAlertCounter, { type: 'alert' } );
				alertController = new mw.echo.Controller( echoApi, alertModelManager );

				mw.echo.ui.alertWidget = new mw.echo.ui.NotificationBadgeWidget(
					alertController,
					alertModelManager,
					links,
					{
						numItems: Number( numAlerts ),
						convertedNumber: badgeLabelAlerts,
						hasUnseen: hasUnseenAlerts,
						badgeIcon: 'bell',
						$overlay: mw.echo.ui.$overlay,
						href: $existingAlertLink.attr( 'href' )
					}
				);

				// Replace the link button with the ooui button
				$existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );

				alertModelManager.on( 'allTalkRead', function () {
					// If there was a talk page notification, get rid of it
					$( '#pt-mytalk a' )
						.removeClass( 'mw-echo-alert' )
						.text( mw.msg( 'mytalk' ) );
				} );

				// listen to event countChange and change title only if polling rate is non-zero
				if ( isLivePollingFeatureEnabledOnWiki() ) {
					alertModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
						alertController.fetchLocalNotifications().then( function () {
							updateBadgeState( alertModelManager, mw.echo.ui.alertWidget );
							if ( userHasOptedInToLiveNotifications() ) {
								latestAlertNotifTime = showNotificationSnippet( alertModelManager, latestAlertNotifTime );
								alertCount = count;
								updateDocumentTitleWithNotificationCount( count, messageCount );
							}
						} );
					} );
				}

				// Load message button and popup if messages exist
				if ( $existingMessageLink.length ) {
					unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
					messageModelManager = new mw.echo.dm.ModelManager( unreadMessageCounter, { type: 'message' } );
					messageController = new mw.echo.Controller( echoApi, messageModelManager );

					mw.echo.ui.messageWidget = new mw.echo.ui.NotificationBadgeWidget(
						messageController,
						messageModelManager,
						links,
						{
							$overlay: mw.echo.ui.$overlay,
							numItems: Number( numMessages ),
							hasUnseen: hasUnseenMessages,
							badgeIcon: 'tray',
							convertedNumber: badgeLabelMessages,
							href: $existingMessageLink.attr( 'href' )
						}
					);

					// Replace the link button with the ooui button
					$existingMessageLink.parent().replaceWith( mw.echo.ui.messageWidget.$element );

					// listen to event countChange and change title only if polling rate is non-zero
					if ( isLivePollingFeatureEnabledOnWiki() ) {
						messageModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
							messageController.fetchLocalNotifications().then( function () {
								updateBadgeState( messageModelManager, mw.echo.ui.messageWidget );
								if ( userHasOptedInToLiveNotifications() ) {
									latestMessageNotifTime = showNotificationSnippet( messageModelManager, latestMessageNotifTime );
									messageCount = count;
									updateDocumentTitleWithNotificationCount( alertCount, count );
								}
							} );
						} );
					}
				}
			} );
			return loadingPromise;

		}

		// Respond to click on the notification button and load the UI on demand
		$( '.mw-echo-notification-badge-nojs' ).on( 'click', function ( e ) {
			var timeOfClick = mw.now(),
				$badge = $( this ),
				clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
			if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
				return false;
			}

			$badge.data( 'clicked', true );

			// Dim the badge while we load
			$badge.addClass( 'mw-echo-notifications-badge-dimmed' );

			// Fire the notification API requests
			echoApi = new mw.echo.api.EchoApi();
			echoApi.fetchNotifications( clickedSection )
				.then( function ( data ) {
					mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
					return data;
				} );

			loadEcho().then( function () {
				// Now that the module loaded, show the popup
				selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
				selectedWidget.once( 'finishLoading', function () {
					// Log timing after notifications are shown
					mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - timeOfClick );
				} );
				selectedWidget.popup.toggle( true );
				mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - timeOfClick );

				if ( hasUnseenAlerts || hasUnseenMessages ) {
					// Clicked on the flyout due to having unread notifications
					// This is part of tracking how likely users are to click a badge with unseen notifications.
					// The other part is the 'echo.unseen' counter, see EchoHooks::onPersonalUrls().
					mw.track( 'counter.MediaWiki.echo.unseen.click' );
				}
			}, function () {
				// Un-dim badge if loading failed
				$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
			} );
			// Prevent default
			return false;
		} );

		function pollForNotificationCountUpdates() {
			alertController.refreshUnreadCount();
			messageController.refreshUnreadCount();
			// Make notification update after n*pollingRate(time in secs) where n depends on document.hidden
			setTimeout( pollForNotificationCountUpdates, ( document.hidden ? 5 : 1 ) * pollingRate * 1000 );
		}

		function pollStart() {
			if ( mw.config.get( 'skin' ) !== 'minerva' && isLivePollingFeatureEnabledOnWiki() ) {
				// load widgets if not loaded already then start polling
				loadEcho().then( pollForNotificationCountUpdates );
			}
		}

		setTimeout( pollStart, 60 * 1000 );

	} );

}

/**
 * Initialise a mobile experience instead
 */
function initMobile() {
	if ( !mw.user.isAnon() ) {
		mw.loader.using( [ 'ext.echo.mobile', 'mobile.startup' ] ).then( function ( require ) {
			require( 'ext.echo.mobile' )();
		} );
	}
}

$( function () {
	if ( mw.config.get( 'wgMFMode' ) ) {
		initMobile();
	} else {
		initDesktop();
	}
} );
<commit_msg>Catch malformed URIs<commit_after>/* eslint-disable no-jquery/no-global-selector */
mw.echo = mw.echo || {};
mw.echo.config = mw.echo.config || {};
// Set default max prioritized action links per item
mw.echo.config.maxPrioritizedActions = 2;

/**
 * Initialise desktop Echo experience
 */
function initDesktop() {
	'use strict';
	var uri;

	// Remove ?markasread=XYZ from the URL
	try {
		uri = new mw.Uri();
		if ( uri.query.markasread !== undefined ) {
			delete uri.query.markasread;
			delete uri.query.markasreadwiki;
			window.history.replaceState( null, document.title, uri );
		}
	} catch ( e ) {
		// Catch problems when the URI is malformed (T261799)
		// e.g. #/media/Fitxer:Campbells_Soup_Cans_MOMA_reduced_80%.jpg
	}

	// Activate ooui
	$( function () {
		var selectedWidget,
			echoApi,
			messageController,
			alertController,
			messageModelManager,
			alertModelManager,
			unreadMessageCounter,
			unreadAlertCounter,
			maxNotificationCount = require( './config.json' ).EchoMaxNotificationCount,
			pollingRate = require( './config.json' ).EchoPollForUpdates,
			documentTitle = document.title,
			$existingAlertLink = $( '#pt-notifications-alert a' ),
			$existingMessageLink = $( '#pt-notifications-notice a' ),
			numAlerts = $existingAlertLink.attr( 'data-counter-num' ),
			numMessages = $existingMessageLink.attr( 'data-counter-num' ),
			badgeLabelAlerts = $existingAlertLink.attr( 'data-counter-text' ),
			badgeLabelMessages = $existingMessageLink.attr( 'data-counter-text' ),
			// eslint-disable-next-line no-jquery/no-class-state
			hasUnseenAlerts = $existingAlertLink.hasClass( 'mw-echo-unseen-notifications' ),
			// eslint-disable-next-line no-jquery/no-class-state
			hasUnseenMessages = $existingMessageLink.hasClass( 'mw-echo-unseen-notifications' ),
			// latestMessageNotifTime is the time of most recent notification that came when we called showNotificationSnippet last
			// the function showNotificationSnippet returns the time of the latest notification and latestMessageNotifTime is updated
			latestMessageNotifTime = new Date(),
			latestAlertNotifTime = new Date(),
			alertCount = parseInt( numAlerts ),
			messageCount = parseInt( numMessages ),
			loadingPromise = null,
			// Store links
			links = {
				notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
				preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
					'#mw-prefsection-echo'
			};

		function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
			var totalCount = totalAlertCount + totalMessageCount,
				convertedTotalCount,
				newTitle = documentTitle;

			if ( totalCount > 0 ) {
				convertedTotalCount = totalCount <= maxNotificationCount ? totalCount : maxNotificationCount + 1;
				convertedTotalCount = mw.msg( 'echo-badge-count', mw.language.convertNumber( convertedTotalCount ) );
				newTitle = mw.msg( 'parentheses', convertedTotalCount ) + ' ' + documentTitle;
			}
			document.title = newTitle;
		}

		/**
		 * Show notification snippet via mw.notify of notifications which came after highestNotifTime.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {Date} highestNotifTime Timestamp of latest notification the last time function was called
		 * @return {Date} Timestamp of latest notification
		 */
		function showNotificationSnippet( modelManager, highestNotifTime ) {
			var timestampAsDate,
				highestTime = new Date();
			highestTime = highestNotifTime;
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				timestampAsDate = new Date( notificationItem.timestamp );
				if ( timestampAsDate > highestNotifTime ) {
					if ( timestampAsDate > highestTime ) {
						highestTime = timestampAsDate;
					}
					if ( !notificationItem.seen ) {
						mw.notify( $.parseHTML( notificationItem.content.header ), { title: mw.msg( 'echo-displaysnippet-title' ) } );
					}
				}
			}
			);
			return highestTime;
		}

		/**
		 * Change the seen state of badges if there are any unseen notifications.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {mw.echo.ui.NotificationBadgeWidget} badgeWidget
		 */
		function updateBadgeState( modelManager, badgeWidget ) {
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				if ( !notificationItem.isSeen() ) {
					badgeWidget.updateBadgeSeenState( true );
				}
			} );
		}

		function isLivePollingFeatureEnabledOnWiki() {
			return pollingRate !== 0;
		}

		/**
		 * User has opted in to preference to show notification snippets and update document title with unread count.
		 *
		 * Only useful when isLivePollingFeatureEnabledOnWiki() returns true.
		 *
		 * @return {boolean} User preference
		 */
		function userHasOptedInToLiveNotifications() {
			return mw.user.options.get( 'echo-show-poll-updates' ) === '1';
		}

		// Change document title on initialization only when polling rate feature flag is non-zero.
		if ( isLivePollingFeatureEnabledOnWiki() && userHasOptedInToLiveNotifications() ) {
			updateDocumentTitleWithNotificationCount( alertCount, messageCount );
		}

		function loadEcho() {
			if ( loadingPromise !== null ) {
				return loadingPromise;
			}
			// This part executes only once, either when header icons are clicked or after completion of 60secs whichever occur first.
			echoApi = new mw.echo.api.EchoApi();

			loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {

				// Overlay
				mw.echo.ui.$overlay.appendTo( document.body );

				unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
				alertModelManager = new mw.echo.dm.ModelManager( unreadAlertCounter, { type: 'alert' } );
				alertController = new mw.echo.Controller( echoApi, alertModelManager );

				mw.echo.ui.alertWidget = new mw.echo.ui.NotificationBadgeWidget(
					alertController,
					alertModelManager,
					links,
					{
						numItems: Number( numAlerts ),
						convertedNumber: badgeLabelAlerts,
						hasUnseen: hasUnseenAlerts,
						badgeIcon: 'bell',
						$overlay: mw.echo.ui.$overlay,
						href: $existingAlertLink.attr( 'href' )
					}
				);

				// Replace the link button with the ooui button
				$existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );

				alertModelManager.on( 'allTalkRead', function () {
					// If there was a talk page notification, get rid of it
					$( '#pt-mytalk a' )
						.removeClass( 'mw-echo-alert' )
						.text( mw.msg( 'mytalk' ) );
				} );

				// listen to event countChange and change title only if polling rate is non-zero
				if ( isLivePollingFeatureEnabledOnWiki() ) {
					alertModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
						alertController.fetchLocalNotifications().then( function () {
							updateBadgeState( alertModelManager, mw.echo.ui.alertWidget );
							if ( userHasOptedInToLiveNotifications() ) {
								latestAlertNotifTime = showNotificationSnippet( alertModelManager, latestAlertNotifTime );
								alertCount = count;
								updateDocumentTitleWithNotificationCount( count, messageCount );
							}
						} );
					} );
				}

				// Load message button and popup if messages exist
				if ( $existingMessageLink.length ) {
					unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
					messageModelManager = new mw.echo.dm.ModelManager( unreadMessageCounter, { type: 'message' } );
					messageController = new mw.echo.Controller( echoApi, messageModelManager );

					mw.echo.ui.messageWidget = new mw.echo.ui.NotificationBadgeWidget(
						messageController,
						messageModelManager,
						links,
						{
							$overlay: mw.echo.ui.$overlay,
							numItems: Number( numMessages ),
							hasUnseen: hasUnseenMessages,
							badgeIcon: 'tray',
							convertedNumber: badgeLabelMessages,
							href: $existingMessageLink.attr( 'href' )
						}
					);

					// Replace the link button with the ooui button
					$existingMessageLink.parent().replaceWith( mw.echo.ui.messageWidget.$element );

					// listen to event countChange and change title only if polling rate is non-zero
					if ( isLivePollingFeatureEnabledOnWiki() ) {
						messageModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
							messageController.fetchLocalNotifications().then( function () {
								updateBadgeState( messageModelManager, mw.echo.ui.messageWidget );
								if ( userHasOptedInToLiveNotifications() ) {
									latestMessageNotifTime = showNotificationSnippet( messageModelManager, latestMessageNotifTime );
									messageCount = count;
									updateDocumentTitleWithNotificationCount( alertCount, count );
								}
							} );
						} );
					}
				}
			} );
			return loadingPromise;

		}

		// Respond to click on the notification button and load the UI on demand
		$( '.mw-echo-notification-badge-nojs' ).on( 'click', function ( e ) {
			var timeOfClick = mw.now(),
				$badge = $( this ),
				clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
			if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
				return false;
			}

			$badge.data( 'clicked', true );

			// Dim the badge while we load
			$badge.addClass( 'mw-echo-notifications-badge-dimmed' );

			// Fire the notification API requests
			echoApi = new mw.echo.api.EchoApi();
			echoApi.fetchNotifications( clickedSection )
				.then( function ( data ) {
					mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
					return data;
				} );

			loadEcho().then( function () {
				// Now that the module loaded, show the popup
				selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
				selectedWidget.once( 'finishLoading', function () {
					// Log timing after notifications are shown
					mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - timeOfClick );
				} );
				selectedWidget.popup.toggle( true );
				mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - timeOfClick );

				if ( hasUnseenAlerts || hasUnseenMessages ) {
					// Clicked on the flyout due to having unread notifications
					// This is part of tracking how likely users are to click a badge with unseen notifications.
					// The other part is the 'echo.unseen' counter, see EchoHooks::onPersonalUrls().
					mw.track( 'counter.MediaWiki.echo.unseen.click' );
				}
			}, function () {
				// Un-dim badge if loading failed
				$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
			} );
			// Prevent default
			return false;
		} );

		function pollForNotificationCountUpdates() {
			alertController.refreshUnreadCount();
			messageController.refreshUnreadCount();
			// Make notification update after n*pollingRate(time in secs) where n depends on document.hidden
			setTimeout( pollForNotificationCountUpdates, ( document.hidden ? 5 : 1 ) * pollingRate * 1000 );
		}

		function pollStart() {
			if ( mw.config.get( 'skin' ) !== 'minerva' && isLivePollingFeatureEnabledOnWiki() ) {
				// load widgets if not loaded already then start polling
				loadEcho().then( pollForNotificationCountUpdates );
			}
		}

		setTimeout( pollStart, 60 * 1000 );

	} );

}

/**
 * Initialise a mobile experience instead
 */
function initMobile() {
	if ( !mw.user.isAnon() ) {
		mw.loader.using( [ 'ext.echo.mobile', 'mobile.startup' ] ).then( function ( require ) {
			require( 'ext.echo.mobile' )();
		} );
	}
}

$( function () {
	if ( mw.config.get( 'wgMFMode' ) ) {
		initMobile();
	} else {
		initDesktop();
	}
} );
<|endoftext|>


<commit_before>/* eslint-disable no-jquery/no-global-selector */
mw.echo = mw.echo || {};
mw.echo.config = mw.echo.config || {};
// Set default max prioritized action links per item
mw.echo.config.maxPrioritizedActions = 2;

/**
 * Initialise desktop Echo experience
 */
function initDesktop() {
	'use strict';

	// Remove ?markasread=XYZ from the URL
	var uri = new mw.Uri();
	if ( uri.query.markasread !== undefined ) {
		delete uri.query.markasread;
		delete uri.query.markasreadwiki;
		window.history.replaceState( null, document.title, uri );
	}

	// Activate ooui
	$( function () {
		var selectedWidget,
			echoApi,
			messageController,
			alertController,
			messageModelManager,
			alertModelManager,
			unreadMessageCounter,
			unreadAlertCounter,
			maxNotificationCount = require( './config.json' ).EchoMaxNotificationCount,
			pollingRate = require( './config.json' ).EchoPollForUpdates,
			documentTitle = document.title,
			$existingAlertLink = $( '#pt-notifications-alert a' ),
			$existingMessageLink = $( '#pt-notifications-notice a' ),
			numAlerts = $existingAlertLink.attr( 'data-counter-num' ),
			numMessages = $existingMessageLink.attr( 'data-counter-num' ),
			badgeLabelAlerts = $existingAlertLink.attr( 'data-counter-text' ),
			badgeLabelMessages = $existingMessageLink.attr( 'data-counter-text' ),
			hasUnseenAlerts = $existingAlertLink.hasClass( 'mw-echo-unseen-notifications' ),
			hasUnseenMessages = $existingMessageLink.hasClass( 'mw-echo-unseen-notifications' ),
			// latestMessageNotifTime is the time of most recent notification that came when we called showNotificationSnippet last
			// the function showNotificationSnippet returns the time of the latest notification and latestMessageNotifTime is updated
			latestMessageNotifTime = new Date(),
			latestAlertNotifTime = new Date(),
			alertCount = parseInt( numAlerts ),
			messageCount = parseInt( numMessages ),
			loadingPromise = null,
			// Store links
			links = {
				notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
				preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
					'#mw-prefsection-echo'
			};

		function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
			var totalCount = totalAlertCount + totalMessageCount,
				convertedTotalCount,
				newTitle = documentTitle;

			if ( totalCount > 0 ) {
				convertedTotalCount = totalCount <= maxNotificationCount ? totalCount : maxNotificationCount + 1;
				convertedTotalCount = mw.msg( 'echo-badge-count', mw.language.convertNumber( convertedTotalCount ) );
				newTitle = mw.msg( 'parentheses', convertedTotalCount ) + ' ' + documentTitle;
			}
			document.title = newTitle;
		}

		/**
		 * Show notification snippet via mw.notify of notifications which came after highestNotifTime.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {Date} highestNotifTime Timestamp of latest notification the last time function was called
		 * @return {Date} Timestamp of latest notification
		 */
		function showNotificationSnippet( modelManager, highestNotifTime ) {
			var timestampAsDate,
				highestTime = new Date();
			highestTime = highestNotifTime;
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				timestampAsDate = new Date( notificationItem.timestamp );
				if ( timestampAsDate > highestNotifTime ) {
					if ( timestampAsDate > highestTime ) {
						highestTime = timestampAsDate;
					}
					if ( !notificationItem.seen ) {
						mw.notify( $.parseHTML( notificationItem.content.header ), { title: mw.msg( 'echo-displaysnippet-title' ) } );
					}
				}
			}
			);
			return highestTime;
		}

		/**
		 * Change the seen state of badges if there are any unseen notifications.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {mw.echo.ui.NotificationBadgeWidget} badgeWidget
		 */
		function updateBadgeState( modelManager, badgeWidget ) {
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				if ( !notificationItem.isSeen() ) {
					badgeWidget.updateBadgeSeenState( true );
				}
			} );
		}

		function isLivePollingFeatureEnabledOnWiki() {
			return pollingRate !== 0;
		}

		/**
		 * User has opted in to preference to show notification snippets and update document title with unread count.
		 *
		 * Only useful when isLivePollingFeatureEnabledOnWiki() returns true.
		 *
		 * @return {boolean} User preference
		 */
		function userHasOptedInToLiveNotifications() {
			return mw.user.options.get( 'echo-show-poll-updates' ) === '1';
		}

		// Change document title on initialization only when polling rate feature flag is non-zero.
		if ( isLivePollingFeatureEnabledOnWiki() && userHasOptedInToLiveNotifications() ) {
			updateDocumentTitleWithNotificationCount( alertCount, messageCount );
		}

		function loadEcho() {
			if ( loadingPromise !== null ) {
				return loadingPromise;
			}
			// This part executes only once, either when header icons are clicked or after completion of 60secs whichever occur first.
			echoApi = new mw.echo.api.EchoApi();

			loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {

				// Overlay
				$( 'body' ).append( mw.echo.ui.$overlay );

				unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
				alertModelManager = new mw.echo.dm.ModelManager( unreadAlertCounter, { type: 'alert' } );
				alertController = new mw.echo.Controller( echoApi, alertModelManager );

				mw.echo.ui.alertWidget = new mw.echo.ui.NotificationBadgeWidget(
					alertController,
					alertModelManager,
					links,
					{
						numItems: Number( numAlerts ),
						convertedNumber: badgeLabelAlerts,
						hasUnseen: hasUnseenAlerts,
						badgeIcon: 'bell',
						$overlay: mw.echo.ui.$overlay,
						href: $existingAlertLink.attr( 'href' )
					}
				);

				// Replace the link button with the ooui button
				$existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );

				alertModelManager.on( 'allTalkRead', function () {
					// If there was a talk page notification, get rid of it
					$( '#pt-mytalk a' )
						.removeClass( 'mw-echo-alert' )
						.text( mw.msg( 'mytalk' ) );
				} );

				// listen to event countChange and change title only if polling rate is non-zero
				if ( isLivePollingFeatureEnabledOnWiki() ) {
					alertModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
						alertController.fetchLocalNotifications().then( function () {
							updateBadgeState( alertModelManager, mw.echo.ui.alertWidget );
							if ( userHasOptedInToLiveNotifications() ) {
								latestAlertNotifTime = showNotificationSnippet( alertModelManager, latestAlertNotifTime );
								alertCount = count;
								updateDocumentTitleWithNotificationCount( count, messageCount );
							}
						} );
					} );
				}

				// Load message button and popup if messages exist
				if ( $existingMessageLink.length ) {
					unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
					messageModelManager = new mw.echo.dm.ModelManager( unreadMessageCounter, { type: 'message' } );
					messageController = new mw.echo.Controller( echoApi, messageModelManager );

					mw.echo.ui.messageWidget = new mw.echo.ui.NotificationBadgeWidget(
						messageController,
						messageModelManager,
						links,
						{
							$overlay: mw.echo.ui.$overlay,
							numItems: Number( numMessages ),
							hasUnseen: hasUnseenMessages,
							badgeIcon: 'tray',
							convertedNumber: badgeLabelMessages,
							href: $existingMessageLink.attr( 'href' )
						}
					);

					// Replace the link button with the ooui button
					$existingMessageLink.parent().replaceWith( mw.echo.ui.messageWidget.$element );

					// listen to event countChange and change title only if polling rate is non-zero
					if ( isLivePollingFeatureEnabledOnWiki() ) {
						messageModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
							messageController.fetchLocalNotifications().then( function () {
								updateBadgeState( messageModelManager, mw.echo.ui.messageWidget );
								if ( userHasOptedInToLiveNotifications() ) {
									latestMessageNotifTime = showNotificationSnippet( messageModelManager, latestMessageNotifTime );
									messageCount = count;
									updateDocumentTitleWithNotificationCount( alertCount, count );
								}
							} );
						} );
					}
				}
			} );
			return loadingPromise;

		}

		// Respond to click on the notification button and load the UI on demand
		$( '.mw-echo-notification-badge-nojs' ).on( 'click', function ( e ) {
			var timeOfClick = mw.now(),
				$badge = $( this ),
				clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
			if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
				return false;
			}

			$badge.data( 'clicked', true );

			// Dim the badge while we load
			$badge.addClass( 'mw-echo-notifications-badge-dimmed' );

			// Fire the notification API requests
			echoApi = new mw.echo.api.EchoApi();
			echoApi.fetchNotifications( clickedSection )
				.then( function ( data ) {
					mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
					return data;
				} );

			loadEcho().then( function () {
				// Now that the module loaded, show the popup
				selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
				selectedWidget.once( 'finishLoading', function () {
					// Log timing after notifications are shown
					mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - timeOfClick );
				} );
				selectedWidget.popup.toggle( true );
				mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - timeOfClick );

				if ( hasUnseenAlerts || hasUnseenMessages ) {
					// Clicked on the flyout due to having unread notifications
					mw.track( 'counter.MediaWiki.echo.unseen.click' );
				}
			}, function () {
				// Un-dim badge if loading failed
				$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
			} );
			// Prevent default
			return false;
		} );

		function pollForNotificationCountUpdates() {
			alertController.refreshUnreadCount();
			messageController.refreshUnreadCount();
			// Make notification update after n*pollingRate(time in secs) where n depends on document.hidden
			setTimeout( pollForNotificationCountUpdates, ( document.hidden ? 5 : 1 ) * pollingRate * 1000 );
		}

		function pollStart() {
			if ( mw.config.get( 'skin' ) !== 'minerva' && isLivePollingFeatureEnabledOnWiki() ) {
				// load widgets if not loaded already then start polling
				loadEcho().then( pollForNotificationCountUpdates );
			}
		}

		setTimeout( pollStart, 60 * 1000 );

	} );

}

/**
 * Initialise a mobile experience instead
 */
function initMobile() {
	if ( !mw.user.isAnon() ) {
		// FIXME: skins.minerva.scripts can be removed from list of dependencies when T221007
		// has been resolved.
		mw.loader.using( [ 'ext.echo.mobile', 'mobile.startup', 'skins.minerva.scripts' ] ).then( function ( require ) {
			var minerva = require( 'skins.minerva.scripts' );
			// This avoids enabling the Echo feature twice.
			// If the Echo code has been removed from Minerva a VERSION number will have been defined.
			// See _____.
			if ( minerva && minerva.VERSION ) {
				require( 'ext.echo.mobile' )();
			}
		} );
	}
}

$( function () {
	if ( mw.config.get( 'wgMFMode' ) ) {
		initMobile();
	} else {
		initDesktop();
	}
} );
<commit_msg>No longer check for skins.minerva.scripts<commit_after>/* eslint-disable no-jquery/no-global-selector */
mw.echo = mw.echo || {};
mw.echo.config = mw.echo.config || {};
// Set default max prioritized action links per item
mw.echo.config.maxPrioritizedActions = 2;

/**
 * Initialise desktop Echo experience
 */
function initDesktop() {
	'use strict';

	// Remove ?markasread=XYZ from the URL
	var uri = new mw.Uri();
	if ( uri.query.markasread !== undefined ) {
		delete uri.query.markasread;
		delete uri.query.markasreadwiki;
		window.history.replaceState( null, document.title, uri );
	}

	// Activate ooui
	$( function () {
		var selectedWidget,
			echoApi,
			messageController,
			alertController,
			messageModelManager,
			alertModelManager,
			unreadMessageCounter,
			unreadAlertCounter,
			maxNotificationCount = require( './config.json' ).EchoMaxNotificationCount,
			pollingRate = require( './config.json' ).EchoPollForUpdates,
			documentTitle = document.title,
			$existingAlertLink = $( '#pt-notifications-alert a' ),
			$existingMessageLink = $( '#pt-notifications-notice a' ),
			numAlerts = $existingAlertLink.attr( 'data-counter-num' ),
			numMessages = $existingMessageLink.attr( 'data-counter-num' ),
			badgeLabelAlerts = $existingAlertLink.attr( 'data-counter-text' ),
			badgeLabelMessages = $existingMessageLink.attr( 'data-counter-text' ),
			hasUnseenAlerts = $existingAlertLink.hasClass( 'mw-echo-unseen-notifications' ),
			hasUnseenMessages = $existingMessageLink.hasClass( 'mw-echo-unseen-notifications' ),
			// latestMessageNotifTime is the time of most recent notification that came when we called showNotificationSnippet last
			// the function showNotificationSnippet returns the time of the latest notification and latestMessageNotifTime is updated
			latestMessageNotifTime = new Date(),
			latestAlertNotifTime = new Date(),
			alertCount = parseInt( numAlerts ),
			messageCount = parseInt( numMessages ),
			loadingPromise = null,
			// Store links
			links = {
				notifications: $existingAlertLink.attr( 'href' ) || mw.util.getUrl( 'Special:Notifications' ),
				preferences: ( $( '#pt-preferences a' ).attr( 'href' ) || mw.util.getUrl( 'Special:Preferences' ) ) +
					'#mw-prefsection-echo'
			};

		function updateDocumentTitleWithNotificationCount( totalAlertCount, totalMessageCount ) {
			var totalCount = totalAlertCount + totalMessageCount,
				convertedTotalCount,
				newTitle = documentTitle;

			if ( totalCount > 0 ) {
				convertedTotalCount = totalCount <= maxNotificationCount ? totalCount : maxNotificationCount + 1;
				convertedTotalCount = mw.msg( 'echo-badge-count', mw.language.convertNumber( convertedTotalCount ) );
				newTitle = mw.msg( 'parentheses', convertedTotalCount ) + ' ' + documentTitle;
			}
			document.title = newTitle;
		}

		/**
		 * Show notification snippet via mw.notify of notifications which came after highestNotifTime.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {Date} highestNotifTime Timestamp of latest notification the last time function was called
		 * @return {Date} Timestamp of latest notification
		 */
		function showNotificationSnippet( modelManager, highestNotifTime ) {
			var timestampAsDate,
				highestTime = new Date();
			highestTime = highestNotifTime;
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				timestampAsDate = new Date( notificationItem.timestamp );
				if ( timestampAsDate > highestNotifTime ) {
					if ( timestampAsDate > highestTime ) {
						highestTime = timestampAsDate;
					}
					if ( !notificationItem.seen ) {
						mw.notify( $.parseHTML( notificationItem.content.header ), { title: mw.msg( 'echo-displaysnippet-title' ) } );
					}
				}
			}
			);
			return highestTime;
		}

		/**
		 * Change the seen state of badges if there are any unseen notifications.
		 *
		 * @param {mw.echo.dm.ModelManager} modelManager
		 * @param {mw.echo.ui.NotificationBadgeWidget} badgeWidget
		 */
		function updateBadgeState( modelManager, badgeWidget ) {
			modelManager.getLocalNotifications().forEach( function ( notificationItem ) {
				if ( !notificationItem.isSeen() ) {
					badgeWidget.updateBadgeSeenState( true );
				}
			} );
		}

		function isLivePollingFeatureEnabledOnWiki() {
			return pollingRate !== 0;
		}

		/**
		 * User has opted in to preference to show notification snippets and update document title with unread count.
		 *
		 * Only useful when isLivePollingFeatureEnabledOnWiki() returns true.
		 *
		 * @return {boolean} User preference
		 */
		function userHasOptedInToLiveNotifications() {
			return mw.user.options.get( 'echo-show-poll-updates' ) === '1';
		}

		// Change document title on initialization only when polling rate feature flag is non-zero.
		if ( isLivePollingFeatureEnabledOnWiki() && userHasOptedInToLiveNotifications() ) {
			updateDocumentTitleWithNotificationCount( alertCount, messageCount );
		}

		function loadEcho() {
			if ( loadingPromise !== null ) {
				return loadingPromise;
			}
			// This part executes only once, either when header icons are clicked or after completion of 60secs whichever occur first.
			echoApi = new mw.echo.api.EchoApi();

			loadingPromise = mw.loader.using( 'ext.echo.ui.desktop' ).then( function () {

				// Overlay
				$( 'body' ).append( mw.echo.ui.$overlay );

				unreadAlertCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'alert', maxNotificationCount );
				alertModelManager = new mw.echo.dm.ModelManager( unreadAlertCounter, { type: 'alert' } );
				alertController = new mw.echo.Controller( echoApi, alertModelManager );

				mw.echo.ui.alertWidget = new mw.echo.ui.NotificationBadgeWidget(
					alertController,
					alertModelManager,
					links,
					{
						numItems: Number( numAlerts ),
						convertedNumber: badgeLabelAlerts,
						hasUnseen: hasUnseenAlerts,
						badgeIcon: 'bell',
						$overlay: mw.echo.ui.$overlay,
						href: $existingAlertLink.attr( 'href' )
					}
				);

				// Replace the link button with the ooui button
				$existingAlertLink.parent().replaceWith( mw.echo.ui.alertWidget.$element );

				alertModelManager.on( 'allTalkRead', function () {
					// If there was a talk page notification, get rid of it
					$( '#pt-mytalk a' )
						.removeClass( 'mw-echo-alert' )
						.text( mw.msg( 'mytalk' ) );
				} );

				// listen to event countChange and change title only if polling rate is non-zero
				if ( isLivePollingFeatureEnabledOnWiki() ) {
					alertModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
						alertController.fetchLocalNotifications().then( function () {
							updateBadgeState( alertModelManager, mw.echo.ui.alertWidget );
							if ( userHasOptedInToLiveNotifications() ) {
								latestAlertNotifTime = showNotificationSnippet( alertModelManager, latestAlertNotifTime );
								alertCount = count;
								updateDocumentTitleWithNotificationCount( count, messageCount );
							}
						} );
					} );
				}

				// Load message button and popup if messages exist
				if ( $existingMessageLink.length ) {
					unreadMessageCounter = new mw.echo.dm.UnreadNotificationCounter( echoApi, 'message', maxNotificationCount );
					messageModelManager = new mw.echo.dm.ModelManager( unreadMessageCounter, { type: 'message' } );
					messageController = new mw.echo.Controller( echoApi, messageModelManager );

					mw.echo.ui.messageWidget = new mw.echo.ui.NotificationBadgeWidget(
						messageController,
						messageModelManager,
						links,
						{
							$overlay: mw.echo.ui.$overlay,
							numItems: Number( numMessages ),
							hasUnseen: hasUnseenMessages,
							badgeIcon: 'tray',
							convertedNumber: badgeLabelMessages,
							href: $existingMessageLink.attr( 'href' )
						}
					);

					// Replace the link button with the ooui button
					$existingMessageLink.parent().replaceWith( mw.echo.ui.messageWidget.$element );

					// listen to event countChange and change title only if polling rate is non-zero
					if ( isLivePollingFeatureEnabledOnWiki() ) {
						messageModelManager.getUnreadCounter().on( 'countChange', function ( count ) {
							messageController.fetchLocalNotifications().then( function () {
								updateBadgeState( messageModelManager, mw.echo.ui.messageWidget );
								if ( userHasOptedInToLiveNotifications() ) {
									latestMessageNotifTime = showNotificationSnippet( messageModelManager, latestMessageNotifTime );
									messageCount = count;
									updateDocumentTitleWithNotificationCount( alertCount, count );
								}
							} );
						} );
					}
				}
			} );
			return loadingPromise;

		}

		// Respond to click on the notification button and load the UI on demand
		$( '.mw-echo-notification-badge-nojs' ).on( 'click', function ( e ) {
			var timeOfClick = mw.now(),
				$badge = $( this ),
				clickedSection = $badge.parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message';
			if ( e.which !== 1 || $badge.data( 'clicked' ) ) {
				return false;
			}

			$badge.data( 'clicked', true );

			// Dim the badge while we load
			$badge.addClass( 'mw-echo-notifications-badge-dimmed' );

			// Fire the notification API requests
			echoApi = new mw.echo.api.EchoApi();
			echoApi.fetchNotifications( clickedSection )
				.then( function ( data ) {
					mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - timeOfClick );
					return data;
				} );

			loadEcho().then( function () {
				// Now that the module loaded, show the popup
				selectedWidget = clickedSection === 'alert' ? mw.echo.ui.alertWidget : mw.echo.ui.messageWidget;
				selectedWidget.once( 'finishLoading', function () {
					// Log timing after notifications are shown
					mw.track( 'timing.MediaWiki.echo.overlay', mw.now() - timeOfClick );
				} );
				selectedWidget.popup.toggle( true );
				mw.track( 'timing.MediaWiki.echo.overlay.ooui', mw.now() - timeOfClick );

				if ( hasUnseenAlerts || hasUnseenMessages ) {
					// Clicked on the flyout due to having unread notifications
					mw.track( 'counter.MediaWiki.echo.unseen.click' );
				}
			}, function () {
				// Un-dim badge if loading failed
				$badge.removeClass( 'mw-echo-notifications-badge-dimmed' );
			} );
			// Prevent default
			return false;
		} );

		function pollForNotificationCountUpdates() {
			alertController.refreshUnreadCount();
			messageController.refreshUnreadCount();
			// Make notification update after n*pollingRate(time in secs) where n depends on document.hidden
			setTimeout( pollForNotificationCountUpdates, ( document.hidden ? 5 : 1 ) * pollingRate * 1000 );
		}

		function pollStart() {
			if ( mw.config.get( 'skin' ) !== 'minerva' && isLivePollingFeatureEnabledOnWiki() ) {
				// load widgets if not loaded already then start polling
				loadEcho().then( pollForNotificationCountUpdates );
			}
		}

		setTimeout( pollStart, 60 * 1000 );

	} );

}

/**
 * Initialise a mobile experience instead
 */
function initMobile() {
	if ( !mw.user.isAnon() ) {
		// FIXME: skins.minerva.scripts can be removed from list of dependencies when T221007
		// has been resolved.
		mw.loader.using( [ 'ext.echo.mobile', 'mobile.startup' ] ).then( function ( require ) {
			require( 'ext.echo.mobile' )();
		} );
	}
}

$( function () {
	if ( mw.config.get( 'wgMFMode' ) ) {
		initMobile();
	} else {
		initDesktop();
	}
} );
<|endoftext|>


<commit_before>import { KeyframeTrack } from '../KeyframeTrack.js';

/**
 * A Track of keyframe values that represent color.
 */

function ColorKeyframeTrack( name, times, values, interpolation ) {

	KeyframeTrack.call( this, name, times, values, interpolation );

}

ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {

	constructor: ColorKeyframeTrack,

	ValueTypeName: 'color'

	// ValueBufferType is inherited

	// DefaultInterpolation is inherited

	// Note: Very basic implementation and nothing special yet.
	// However, this is the place for color space parameterization.

} );

export { ColorKeyframeTrack };
<commit_msg>ColorKeyframeTrack: Convert to es6 class<commit_after>import { KeyframeTrack } from '../KeyframeTrack.js';

/**
 * A Track of keyframe values that represent color.
 */
class ColorKeyframeTrack extends KeyframeTrack {}

Object.assign( ColorKeyframeTrack.prototype, {

	ValueTypeName: 'color'

	// ValueBufferType is inherited

	// DefaultInterpolation is inherited

	// Note: Very basic implementation and nothing special yet.
	// However, this is the place for color space parameterization.

} );

export { ColorKeyframeTrack };
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import MediaCard from './media/MediaCard';
import Annotation from './source/Annotation';
import AddAnnotation from './source/AddAnnotation';

class MediasAndAnnotations extends Component {
  constructor(props) {
    super(props);
    this.state = { items: [] };
  }

  compare(a, b) {
    if (a.timestamp  b.timestamp) {
      return -1;
    }
    return 0;
  }

  sort(annotations, medias) {
    var items = [];

    annotations.forEach(function(annotation) {
      var item = annotation;
      item.itemType = 'annotation';
      item.timestamp = new Date(item.node.created_at).getTime();
      items.push(item);
    });

    medias.forEach(function(media) {
      var item = media;
      item.itemType = 'media';
      item.timestamp = parseInt(item.node.published, 10) * 1000;
      items.push(item);
    });

    items.sort(this.compare);
    this.setState({ items: items });
  }

  componentWillMount() {
    this.sort(this.props.annotations, this.props.medias);
  }

  componentWillReceiveProps(nextProps) {
    this.sort(nextProps.annotations, nextProps.medias);
  }

  render() {
    const that = this;
    const props = that.props;

    return (

        {that.state.items.map(function(item) {

          if (item.itemType == 'annotation') {
            return (

<Annotation annotation={item.node} annotated={props.annotated} annotatedType={props.annotatedType} />

            );
          }

          else if (item.itemType == 'media') {
            return (

<MediaCard media={item.node} annotated={props.annotated} annotatedType={props.annotatedType} />

            );
          }

        })}

        <AddAnnotation annotated={props.annotated} annotatedType={props.annotatedType} types={props.types} />

    );
  }
}

export default MediasAndAnnotations;
<commit_msg>Reverse sort order of media annotations<commit_after>import React, { Component, PropTypes } from 'react';
import MediaCard from './media/MediaCard';
import Annotation from './source/Annotation';
import AddAnnotation from './source/AddAnnotation';

class MediasAndAnnotations extends Component {
  constructor(props) {
    super(props);
    this.state = { items: [] };
  }

  compare(a, b) {
    if (a.timestamp > b.timestamp) {
      return 1;
    }
    if (a.timestamp < b.timestamp) {
      return -1;
    }
    return 0;
  }

  sort(annotations, medias) {
    var items = [];

    annotations.forEach(function(annotation) {
      var item = annotation;
      item.itemType = 'annotation';
      item.timestamp = new Date(item.node.created_at).getTime();
      items.push(item);
    });

    medias.forEach(function(media) {
      var item = media;
      item.itemType = 'media';
      item.timestamp = parseInt(item.node.published, 10) * 1000;
      items.push(item);
    });

    items.sort(this.compare);
    this.setState({ items: items });
  }

  componentWillMount() {
    this.sort(this.props.annotations, this.props.medias);
  }

  componentWillReceiveProps(nextProps) {
    this.sort(nextProps.annotations, nextProps.medias);
  }

  render() {
    const that = this;
    const props = that.props;

    return (

        {that.state.items.map(function(item) {

          if (item.itemType == 'annotation') {
            return (

<Annotation annotation={item.node} annotated={props.annotated} annotatedType={props.annotatedType} />

            );
          }

          else if (item.itemType == 'media') {
            return (

<MediaCard media={item.node} annotated={props.annotated} annotatedType={props.annotatedType} />

            );
          }

        })}

        <AddAnnotation annotated={props.annotated} annotatedType={props.annotatedType} types={props.types} />

    );
  }
}

export default MediasAndAnnotations;
<|endoftext|>


<commit_before>$(document).ready(function(){
	//BEGIN GLOBAL VARIABLES
	var quesIDs;
	var curQues;
	var totalQues;
	var usrAns;
	var ans;
	//END
	init()

	function init() {
		//VAR DECS
		quesIDs = [];
		curQues = 0;
		totalQues = 5;
		//PAGE STYLING
		$('#submit').prop('disabled', false);
		$('#submit').val("Submit");
		//INIT
		generateQuesIDs(totalQues);
		displayQues();
	}

	$('input[name=debug]').click(function(){
		if ($('input[name=debug]').is(':checked')) {
			$('#submit').val("CurQues: " + curQues);
		} else {
			$('#submit').val("Submit");
		}
	});

	$('#submit').click(function(){
		if ($('input[name=selection]').is(':checked') && !($('input[name=debug]').is(':checked'))) {
			checkAns()
		} else if ($('input[name=debug]').is(':checked')) { //DEBUG
			displayQues(); //DEBUG
			$('#submit').val("CurQues: " + curQues); //DEBUG
		} else {
			alert("Select an answer!")
		}
	})

	function checkAns() {
		usrAns = $('input[name=selection]:checked').attr('id');
		ans = quDB.questions[quesIDs[curQues]].ans;
		if (usrAns == ans) {
			alert("Correct!"); //DEBUG
		} else {
			alert("Incorrect!\nThe answer was " + ans); //DEBUG
		}
		displayQues()
	}

	function generateQuesIDs(amt) {
		for (var i=0;i<=amt;i++) {
			quesIDs[i] = Math.floor(Math.random()*20);
			while (quesIDs[i] == quesIDs[i-1]) {
				quesIDs[i] = Math.floor(Math.random()*20);
			}
		}
	}

	function displayQues() {
		if (curQues == totalQues) {
			finish()
		} else {
			curQues++;
			var qu = quDB.questions[quesIDs[curQues]].qu;
			var c1 = quDB.questions[quesIDs[curQues]].c1;
			var c2 = quDB.questions[quesIDs[curQues]].c2;
			var c3 = quDB.questions[quesIDs[curQues]].c3;
			var c4 = quDB.questions[quesIDs[curQues]].c4;
			$('.question').html(qu);
			$('#labl_c1').html(c1);
			$('#labl_c2').html(c2);
			$('#labl_c3').html(c3);
			$('#labl_c4').html(c4);
		}
	}

	function finish() {
		$('#submit').prop('disabled', true);
		alert("You've answered all the questions!");
	}
})
<commit_msg>Animations! Styling! Validation!<commit_after>$(document).ready(function(){
	//BEGIN GLOBAL VARIABLES
	var quesIDs;
	var curQues;
	var totalQues;
	var usrAns;
	var ans;
	//END
	init()

	function init() {
		//STOP PREVIOUS CODE
		$('.timeBar').stop(false,false);
		//VAR DECS
		quesIDs = [];
		curQues = 0;
		totalQues = 5;
		//PAGE STYLING
		$('#submit').prop('disabled', false);
		$('#submit').val("Submit");
		$('.timeBar').height('240px');
		//INIT
		generateQuesIDs(totalQues);
		displayQues();
		timer();
	}

	$('input[name=debug]').click(function(){
		if ($('input[name=debug]').is(':checked')) {
			$('#submit').val("CurQues: " + curQues);
		} else {
			$('#submit').val("Submit");
		}
	});

	$('#submit').click(function(){
		if ($('input[name=selection]').is(':checked') && !($('input[name=debug]').is(':checked'))) {
			checkAns()
		} else if ($('input[name=debug]').is(':checked')) { //DEBUG
			displayQues(); //DEBUG
			$('#submit').val("CurQues: " + curQues); //DEBUG
		} else {
			alert("Select an answer!")
		}
	})

	function checkAns() {
		usrAns = $('input[name=selection]:checked').attr('id');
		ans = quDB.questions[quesIDs[curQues]].ans;
		if (usrAns == ans) {
			alert("Correct!"); //DEBUG
		} else {
			alert("Incorrect!\nThe answer was " + ans); //DEBUG
		}
		displayQues()
	}

	function generateQuesIDs(amt) {
		for (var i=0;i<=amt;i++) {
			quesIDs[i] = Math.floor(Math.random()*20);
			while (isIDUsed(quesIDs[i],i) && i !== 0) {
				quesIDs[i] = Math.floor(Math.random()*20);
			}
		}
	}

	function isIDUsed(id,curID) {
		for (var i = 0;i<curID;i++) {
		  if (id == quesIDs[i]) {
			  return true;
		  }
		}
		return false;
	}

	function displayQues() {
		if (curQues == totalQues) {
			finish()
		} else {
			curQues++;
			var qu = quDB.questions[quesIDs[curQues]].qu;
			var c1 = quDB.questions[quesIDs[curQues]].c1;
			var c2 = quDB.questions[quesIDs[curQues]].c2;
			var c3 = quDB.questions[quesIDs[curQues]].c3;
			var c4 = quDB.questions[quesIDs[curQues]].c4;
			$('.question').html(qu);
			$('#labl_c1').html(c1);
			$('#labl_c2').html(c2);
			$('#labl_c3').html(c3);
			$('#labl_c4').html(c4);
		}
	}

	function timer() {
		$('.timeBar').animate({'height': '0px', 'marginTop': '240px'},10000,"linear",function(){
			alert("You took too long!");
			displayQues();
		});
	}

	function finish() {
		$('#submit').prop('disabled', true);
		alert("You've answered all the questions!");
	}
})
<|endoftext|>


<commit_before>define( [ "ember" ], function( Ember ) {

	var	get = Ember.get,
		set = Ember.set;

	var	CSSMediaRule		= window.CSSMediaRule,
		CSSStyleRule		= window.CSSStyleRule,
		reMinWidth			= /^(?:\(max-width:\s*\d+px\)\s*and\s*)?\(min-width:\s*(\d+)px\)$/,
		cachedMinWidths		= {},
		cssMinWidthRules	= [].filter.call( document.styleSheets[0].rules, function( rule ) {
			return	rule instanceof CSSMediaRule
				&&	rule.media.length === 1
				&&	reMinWidth.test( rule.media[0] )
				&&	rule.cssRules.length > 0;
		}),
		cachedMinHeights	= [].filter.call( document.styleSheets[0].rules, function( rule ) {
			return	rule instanceof CSSStyleRule
				&&	rule.style.minHeight !== "";
		}).reduce(function( cache, rule ) {
			cache[ rule.selectorText ] = parseInt( rule.style.minHeight );
			return cache;
		}, {} );

	/**
	 * Generate a list of all min-width media queries and their item widths for a specific selector.
	 * These media queries have been defined by the lesscss function .dynamic-elems-per-row()
	 */
	function readMinWidths( selector ) {
		if ( cachedMinWidths.hasOwnProperty( selector ) ) {
			return cachedMinWidths[ selector ];
		}

		var data = cssMinWidthRules
			.filter(function( rule ) {
				return rule.cssRules[0].selectorText === selector;
			})
			.map(function( rule ) {
				return {
					minWidth: Math.floor( reMinWidth.exec( rule.media[0] )[1] ),
					numItems: Math.floor( 100 / parseInt( rule.cssRules[0].style.width ) )
				};
			});

		if ( !data.length ) {
			throw new Error( "Invalid selector" );
		}

		return ( cachedMinWidths[ selector ] = data );
	}

	function readMinHeights( selector ) {
		if ( !cachedMinHeights.hasOwnProperty( selector ) ) {
			throw new Error( "Invalid selector" );
		}
		return cachedMinHeights[ selector ];
	}

	function getNeededColumns( selector ) {
		return readMinWidths( selector ).reduce(function( current, next ) {
			return window.innerWidth < next.minWidth
				? current
				: next;
		}).numItems;
	}

	function getNeededRows( selector ) {
		var minHeight = readMinHeights( selector );
		return 1 + Math.ceil( getItemContainer().clientHeight / minHeight );
	}

	function getItemContainer() {
		// the route's view hasn't been inserted yet: choose the parent element
		return	document.querySelector( "body > .wrapper" )
			||	document.body;
	}

	return Ember.Mixin.create({
		/**
		 * Define the content array location.
		 * Can't use a binding here!!!
		 */
		contentPath: "controller.model",

		/**
		 * Don't fetch infinitely.
		 */
		maxAutoFetches: 3,

		/**
		 * This is actually an ugly concept, but we need to set this data at the route, so we can
		 * control the fetch size before querying the data. The fetch size depends on the
		 * window size and css media queries containing this selector. We can't move this
		 * calculation to the view or to the controller, because they both aren't set up at the
		 * time where the size is being calculated...
		 */
		itemSelector: Ember.required(),

		/**
		 * Calculate how many items are needed to completely fill the container
		 */
		calcFetchSize: function() {
			var	itemSel	= get( this, "itemSelector" ),
				columns	= getNeededColumns( itemSel ),
				rows	= getNeededRows( itemSel ),
				offset	= get( this, "offset" ),
				uneven	= offset % columns,
				limit	= ( columns * rows ) + ( uneven > 0 ? columns - uneven : 0 );

			// fetch size + number of items to fill the last row after a window resize
			set( this, "limit", limit );
		},

		beforeModel: function() {
			this._super.apply( this, arguments );

			// reset on route change
			set( this, "offset", 0 );
			this.calcFetchSize();
		},

		setupController: function( controller ) {
			this._super.apply( this, arguments );

			// late bindings
			var binding = get( this, "_binding_offset" );
			if ( !binding ) {
				binding = Ember.Binding.from(
					"%@.length".fmt( get( this, "contentPath" ) )
				).to( "offset" );
				set( this, "_binding_offset", binding );
			}
			binding.connect( this );

			var	offset	= get( this, "offset" ),
				limit	= get( this, "limit" );

			set( controller, "isFetching", false );
			set( controller, "hasFetchedAll", offset < limit );
		},

		fetchContent: function() {
			return this.model();
		},

		actions: {
			"willTransition": function () {
				var binding = get( this, "_binding_offset" );
				if ( binding ) {
					binding.disconnect( this );
				}
			},

			"willFetchContent": function( force ) {
				var	controller	= get( this, "controller" ),
					isFetching	= get( controller, "isFetching" ),
					fetchedAll	= get( controller, "hasFetchedAll" );

				// we're already busy or finished fetching
				if ( isFetching || fetchedAll ) { return; }

				this.calcFetchSize();

				var	content	= get( this, get( this, "contentPath" ) ),
					offset	= get( this, "offset" ),
					limit	= get( this, "limit" ),
					max		= get( this, "maxAutoFetches" ),
					num		= offset / limit;

				// don't fetch infinitely
				if ( !force && num > max ) { return; }

				set( controller, "fetchError", false );
				set( controller, "isFetching", true );

				// fetch content
				this.fetchContent()
					.then(function( data ) {
						if ( !data || !data.length || data.length < limit ) {
							set( controller, "hasFetchedAll", true );
						} else {
							content.pushObjects( data );
						}
						set( controller, "isFetching", false );
					})
					.catch(function( err ) {
						set( controller, "fetchError", true );
						set( controller, "isFetching", false );
						set( controller, "hasFetchedAll", false );
						return Promise.reject( err );
					});
			}
		}
	});

});
<commit_msg>Always append fetched data. Resolves #34<commit_after>define( [ "ember" ], function( Ember ) {

	var	get = Ember.get,
		set = Ember.set;

	var	CSSMediaRule		= window.CSSMediaRule,
		CSSStyleRule		= window.CSSStyleRule,
		reMinWidth			= /^(?:\(max-width:\s*\d+px\)\s*and\s*)?\(min-width:\s*(\d+)px\)$/,
		cachedMinWidths		= {},
		cssMinWidthRules	= [].filter.call( document.styleSheets[0].rules, function( rule ) {
			return	rule instanceof CSSMediaRule
				&&	rule.media.length === 1
				&&	reMinWidth.test( rule.media[0] )
				&&	rule.cssRules.length > 0;
		}),
		cachedMinHeights	= [].filter.call( document.styleSheets[0].rules, function( rule ) {
			return	rule instanceof CSSStyleRule
				&&	rule.style.minHeight !== "";
		}).reduce(function( cache, rule ) {
			cache[ rule.selectorText ] = parseInt( rule.style.minHeight );
			return cache;
		}, {} );

	/**
	 * Generate a list of all min-width media queries and their item widths for a specific selector.
	 * These media queries have been defined by the lesscss function .dynamic-elems-per-row()
	 */
	function readMinWidths( selector ) {
		if ( cachedMinWidths.hasOwnProperty( selector ) ) {
			return cachedMinWidths[ selector ];
		}

		var data = cssMinWidthRules
			.filter(function( rule ) {
				return rule.cssRules[0].selectorText === selector;
			})
			.map(function( rule ) {
				return {
					minWidth: Math.floor( reMinWidth.exec( rule.media[0] )[1] ),
					numItems: Math.floor( 100 / parseInt( rule.cssRules[0].style.width ) )
				};
			});

		if ( !data.length ) {
			throw new Error( "Invalid selector" );
		}

		return ( cachedMinWidths[ selector ] = data );
	}

	function readMinHeights( selector ) {
		if ( !cachedMinHeights.hasOwnProperty( selector ) ) {
			throw new Error( "Invalid selector" );
		}
		return cachedMinHeights[ selector ];
	}

	function getNeededColumns( selector ) {
		return readMinWidths( selector ).reduce(function( current, next ) {
			return window.innerWidth < next.minWidth
				? current
				: next;
		}).numItems;
	}

	function getNeededRows( selector ) {
		var minHeight = readMinHeights( selector );
		return 1 + Math.ceil( getItemContainer().clientHeight / minHeight );
	}

	function getItemContainer() {
		// the route's view hasn't been inserted yet: choose the parent element
		return	document.querySelector( "body > .wrapper" )
			||	document.body;
	}

	return Ember.Mixin.create({
		/**
		 * Define the content array location.
		 * Can't use a binding here!!!
		 */
		contentPath: "controller.model",

		/**
		 * Don't fetch infinitely.
		 */
		maxAutoFetches: 3,

		/**
		 * This is actually an ugly concept, but we need to set this data at the route, so we can
		 * control the fetch size before querying the data. The fetch size depends on the
		 * window size and css media queries containing this selector. We can't move this
		 * calculation to the view or to the controller, because they both aren't set up at the
		 * time where the size is being calculated...
		 */
		itemSelector: Ember.required(),

		/**
		 * Calculate how many items are needed to completely fill the container
		 */
		calcFetchSize: function() {
			var	itemSel	= get( this, "itemSelector" ),
				columns	= getNeededColumns( itemSel ),
				rows	= getNeededRows( itemSel ),
				offset	= get( this, "offset" ),
				uneven	= offset % columns,
				limit	= ( columns * rows ) + ( uneven > 0 ? columns - uneven : 0 );

			// fetch size + number of items to fill the last row after a window resize
			set( this, "limit", limit );
		},

		beforeModel: function() {
			this._super.apply( this, arguments );

			// reset on route change
			set( this, "offset", 0 );
			this.calcFetchSize();
		},

		setupController: function( controller ) {
			this._super.apply( this, arguments );

			// late bindings
			var binding = get( this, "_binding_offset" );
			if ( !binding ) {
				binding = Ember.Binding.from(
					"%@.length".fmt( get( this, "contentPath" ) )
				).to( "offset" );
				set( this, "_binding_offset", binding );
			}
			binding.connect( this );

			var	offset	= get( this, "offset" ),
				limit	= get( this, "limit" );

			set( controller, "isFetching", false );
			set( controller, "hasFetchedAll", offset < limit );
		},

		fetchContent: function() {
			return this.model();
		},

		actions: {
			"willTransition": function () {
				var binding = get( this, "_binding_offset" );
				if ( binding ) {
					binding.disconnect( this );
				}
			},

			"willFetchContent": function( force ) {
				var	controller	= get( this, "controller" ),
					isFetching	= get( controller, "isFetching" ),
					fetchedAll	= get( controller, "hasFetchedAll" );

				// we're already busy or finished fetching
				if ( isFetching || fetchedAll ) { return; }

				this.calcFetchSize();

				var	content	= get( this, get( this, "contentPath" ) ),
					offset	= get( this, "offset" ),
					limit	= get( this, "limit" ),
					max		= get( this, "maxAutoFetches" ),
					num		= offset / limit;

				// don't fetch infinitely
				if ( !force && num > max ) { return; }

				set( controller, "fetchError", false );
				set( controller, "isFetching", true );

				// fetch content
				this.fetchContent()
					.then(function( data ) {
						if ( !data || !data.length || data.length < limit ) {
							set( controller, "hasFetchedAll", true );
						}
						if ( data && data.length ) {
							content.pushObjects( data );
						}
						set( controller, "isFetching", false );
					})
					.catch(function( err ) {
						set( controller, "fetchError", true );
						set( controller, "isFetching", false );
						set( controller, "hasFetchedAll", false );
						return Promise.reject( err );
					});
			}
		}
	});

});
<|endoftext|>