<commit_before>/**
 * jQuery.MediaAlignedText
 * Copyright (c) 2012 J. Reuben Wetherbee - jrweth(at)gmail(dot)com
 * Licensed under MIT
 *
 * @projectDescription Handles the playback of MediaFiles along with aligned text
 *
 * @todo more documentation needed
 */

(function( $ ) {

    /**
     * base JQuery function to handle public method calls
     */
    $.fn.mediaAlignedText = function( method ) {

        //call specific method and pass along additional arguments
        if(public_methods[method]) {
            return public_methods[method].apply(this, Array.prototype.slice.call( arguments, 1));
        }
        //default to init method if no method specified
        if ( typeof method === 'object' || ! method ) {

            return public_methods.init.apply( this, arguments );
        }
        //method not recognized
        else {
            $.error( 'Method ' +  method + ' does not exist in jQuery.mediaAlignedText' );
        }
    };

    /**
     * Define the following public methods
     * - init              Initialize the MediaAlignedText
     * - charGroupClicked  Handle clicking the CharGroup
     */
    var public_methods = {
        //initialize the MediaAlignedText playback
        init : function(options){

            //get default options
            var options = $.extend({
                'jplayer_controls_id'       : 'mat_jplayer_controls',//id of the div where jplayer controls reside
                'media_files'               : {},                    //array of media files and types to initiate
                'text_viewer_css_selector'  : '#mat_text_viewer',    //id of the div where the text is displayed
                'jplayer_options'           : {},                    //additional jplayer options to initiate
                'jplayer_control_options'   : {},                    //options to send to the jplayer control generator
                'generate_jplayer_controls' : true,                  //flag indicating if controls should be generated or not
                'highlight_function'        : _textSegmentHighlight, //the function to use to highlight - requires object and text_segment_id as arguments
                'highlight_remove_function' : _textSegmentRemoveHighlight,  //function to remove highligh - requires object and text_segment_id as arguments
                'time_start_attribute'      : 'data-time_start',
                'time_end_attribute'        : 'data-time_end'
            }, options);

            //save options to the objects namespaced data holder
            var $this = $(this);
            var data = $this.data('mediaAlignedText');

            if(!data) {
                $this.data('mediaAlignedText', options);
            }

            //initialized jplayer controls
            if(options.generate_jplayer_controls) {
                if(options.media_files[0] != undefined && options.media_files[0].title != undefined) {
                    options.jplayer_control_options.title = options.media_files[0].title;
                }
                $('#'+options.jplayer_controls_id).html(_getJplayerContainerHtml(options.jplayer_control_options));
            }

            //initialize all the mappings and components
            _initTextSegments($this);
            _initJplayer($this);

        },

        //handles advancing to the aligned time of media when charGroup div clicked
        'textSegmentClicked' : function(text_segment_index) {

            var $this = $(this);
            var data = $this.data('mediaAlignedText');

            if( text_segment_index) {
                var text_segment = data.text_segments[text_segment_index];
                _setCurrentTextSegment($this, text_segment_index);

                if(text_segment.time_start >= 0) {
                    //go to the place in the media file
                    if($this.data('jPlayer').status.paused) { //if player paused just advance
                        $this.jPlayer('pause', text_segment.time_start/1000);
                    }
                    else { // if player not paused then go to
                        $this.jPlayer('play', text_segment.time_start/1000);
                    }
                }
            }
        },
        /**
         * play the media file for just the text segment
         * @param integer  text_segment_index   Index of the text_segment to be played
         */
        'playTextSegment' : function(text_segment_index) {
            var $this = $(this);
            var text_segment = $this.data('mediaAlignedText').text_segments[text_segment_index];
            $this.jPlayer('play', text_segment.time_start / 1000);

            //stop the jPlayer when segment is done
            var t = setTimeout("$('#" + $this.attr('id') + "').jPlayer('pause')", text_segment.time_end - text_segment.time_start);
        },

        'refreshSegments' : function() {
            var $this = $(this);
            //initialize all the mappings and components
            _initTextSegments($this);

        }
    };

    var _checkTimeChange = function($this){
        var data = $this.data('mediaAlignedText');

        var current_time = parseFloat($this.data("jPlayer").status.currentTime)*1000;
        var current_text_segment_invalid = false;

        //get current start and end times as a convenience
        if(data.current_text_segment_index == undefined) {
            var current_segment_start = -1;
            var current_segment_end = -1;
        }
        else {
            var segment = data.text_segments[data.current_text_segment_index];
            var current_segment_start = parseFloat(segment.time_start);
            var current_segment_end = parseFloat(segment.time_end);
        }

        //check if we are still in the timeframe of the currently selected text segment
        if(current_time >= current_segment_start && current_time <= current_segment_end) {
            return true;
        }
        else {    //unset the current text segment to remove highlight
            if(data.current_text_segment_index != undefined) {

                data.highlight_remove_function($this, data.current_text_segment_index);
                //unset the current_text_segment if necessary
                data.current_text_segment_index = undefined;
                current_text_segment_invalid = true;

            }

            //set up search for new text_segment starting from current segment and proceeding accordingly
            if(data.current_text_segment_index == undefined) {
                var start = 0;
                var step = 1;
                var end = data.text_segments.length -1;
            }
            else if (current_time > current_segment_end) {
                var start = data.current_text_segment_index;
                var step = 1;
                var end = data.text_segments.length -1;
            }
            else {
                var start = data.current_text_segment_index;
                var step = -1;
                var end = 0;
            }

            //loop through and search for the next matching segment
            for(i = start; i != end; i = i + step) {

                //set the text_segment var for convenience
                var text_segment = data.text_segments[i];
                if(text_segment.time_start < current_time && text_segment.time_end > current_time) {
                    _setCurrentTextSegment($this, i);
                    //check for the next match
                    return(true);
                }
                //check to see if we are seeking forwards and we've passed it already
                else if(step == 1 && text_segment.time_start > current_time){
                    break;
                }
                //check to see if we are seeking backwards and we've passed it already
                else if(step == -1 && text_segment.time_end < current_time) {
                    break;
                }
            }
        }
        //no match - must not have found one - save here to make sure current is unset
        $this.data('mediaAlignedText', data);
    };

    //initialize the Jplayer
    var _initJplayer = function ($this) {

        var data = $this.data('mediaAlignedText');

        //get the media types supplied by looking at the first media file
        var types_supplied = '';

        //check to see if the media file is defined
        if(data.media_files == undefined) {
            types_supplied = 'mp3';
        }
        else {
            for(i in data.media_files[0].media) {
                types_supplied = types_supplied + i + ',';
            }

            //load the media file
            data.jplayer_options.ready = function() {
                $(this).jPlayer("setMedia", data.media_files[0].media);
            };

            //knock off the last comma
            types_supplied = types_supplied.substring(0, types_supplied.length-1);
        }

        //initiate the jplayer
        data.jplayer_options = $.extend({
              swfPath: 'js',
              supplied: types_supplied,
              timeupdate: function() {_checkTimeChange($(this));}
        }, data.jplayer_options);

        $this.jPlayer(data.jplayer_options);
    };

    /**
     * initialize an ordered index of text segments
     * @todo it is assumed the text segments are listed in time order - should be checked
     */
    var _initTextSegments = function($this) {
        var data = $this.data('mediaAlignedText');
        var order = 0;
        var text_segments = new Array();

        $(data.text_viewer_css_selector + ' [' + data.time_start_attribute + ']').each(function(index) {
            var $this = $(this);
            $this.addClass('mat_text_segment');
            $this.attr('data-mat_segment', index);
            text_segments[index] = {'time_start': parseFloat($this.attr(data.time_start_attribute))};

            if($this.attr(data.time_end_attribute)) {
                text_segments[index].time_end = $this.attr(data.time_end_attribute);
            }

            //add end time to previous segment if not yet defined
            if(index > 0 && text_segments[index-1].time_end == undefined) {
                text_segments[index-1].time_end = parseFloat($this.attr(data.time_start_attribute));
            }
        });

        //set the time end for the last segment
        if(text_segments.length && text_segments[text_segments.length - 1].time_end == undefined) {
            //if time_start is undefined or -1 set to -1
            if(text_segments[text_segments.length - 1].time_start >= 0 && data.media_files[0].duration > 0) {
                text_segments[text_segments.length - 1].time_end = data.media_files[0].duration;
            }
            else { //set to the start time
                text_segments[text_segments.length -1].time_end = text_segments[text_segments.length -1].time_start;
            }
        }

        $(data.text_viewer_css_selector).on(
            'click.mediaAlignedText',
            '[' + data.time_start_attribute +']',
            {'parent' : $this},
            function(event) {
                event.data.parent.mediaAlignedText('textSegmentClicked', $(this).attr('data-mat_segment'));
        });
        data.text_segments = text_segments;

        //write back to the data object
        $this.data('mediaAlignedText', data);
    }

    /**
     * get the default html for the jplayer media player
     *
     * @param Object   options  The options that can be used to initialize the Jplayer Controller including the following
     * - jp_container_id:  The id of the container
     * - title:            The media file title to include in the player
     * -
     */
    var _getJplayerContainerHtml = function (options) {

            var options = $.extend({
                'jp_container_id' : 'jp_container_1',  //the id for the container for the controls
                'title'           : '',  //the title of the media file
                'media_type'      : 'audio'  //the media type for the container
            }, options);

            return '
\

\

\

\

play
\

pause
\

stop
\

mute
\

unmute
\

max volume
\

\

\

\

\

\

\

\

\

\

\

\

\

\

repeat
\

repeat off
\

\

\

\

\

\

'+options.title+'
\

\

\

\
              Update Required\
              To play the media you will need to either update your browser to a recent version or update your Flash plugin.\

\

\

';
    };

    /**
     * Set the current text segment to be the one supplied
     *
     * @param jQueryObject     $this   The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  Theindex of the textSegment to be highlighted
     */
    var _setCurrentTextSegment = function($this, text_segment_index) {
        var data = $this.data('mediaAlignedText');

        //if it is already set than do nothing
        if (data.current_text_segment_index == text_segment_index) {
            return true;
        }
        //if current text segment already set unhighlight it
        if (data.current_text_segment_index != undefined) {
            data.highlight_remove_function($this, data.current_text_segment_index);
        }

        //set new values for current and then highlight
        data.current_text_segment_index= text_segment_index;
        $this.data('mediaAlignedText', data);

        data.highlight_function($this, text_segment_index);

    };

    /**
     * Highlight a particular text segment
     *
     * @param jQueryObject     $this    The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  The index of the textSegment to be highlighted
     */
    var _textSegmentHighlight = function($this, text_segment_index) {
        $('[data-mat_segment='+text_segment_index+']').addClass('mat_highlighted_text_segment');
        $($this.data('mediaAlignedText').text_viewer_css_selector).scrollTo('.mat_highlighted_text_segment', 250, {'axis': 'y', 'offset': -20});
    };

    /**
     * Remove the highlight on a particular text segment
     *
     * @param jQueryObject     $this   The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  The id of the textSegment to have highlighting removed
     */
    var _textSegmentRemoveHighlight = function($this, text_segment_index){
        $('[data-mat_segment='+text_segment_index+']').removeClass('mat_highlighted_text_segment');
    };

})( jQuery );
<commit_msg>added ability to disable checking of time<commit_after>/**
 * jQuery.MediaAlignedText
 * Copyright (c) 2012 J. Reuben Wetherbee - jrweth(at)gmail(dot)com
 * Licensed under MIT
 *
 * @projectDescription Handles the playback of MediaFiles along with aligned text
 *
 * @todo more documentation needed
 */

(function( $ ) {

    /**
     * base JQuery function to handle public method calls
     */
    $.fn.mediaAlignedText = function( method ) {

        //call specific method and pass along additional arguments
        if(public_methods[method]) {
            return public_methods[method].apply(this, Array.prototype.slice.call( arguments, 1));
        }
        //default to init method if no method specified
        if ( typeof method === 'object' || ! method ) {

            return public_methods.init.apply( this, arguments );
        }
        //method not recognized
        else {
            $.error( 'Method ' +  method + ' does not exist in jQuery.mediaAlignedText' );
        }
    };

    /**
     * Define the following public methods
     * - init              Initialize the MediaAlignedText
     * - charGroupClicked  Handle clicking the CharGroup
     */
    var public_methods = {
        //initialize the MediaAlignedText playback
        init : function(options){

            //get default options
            var options = $.extend({
                'jplayer_controls_id'       : 'mat_jplayer_controls',//id of the div where jplayer controls reside
                'media_files'               : {},                    //array of media files and types to initiate
                'text_viewer_css_selector'  : '#mat_text_viewer',    //id of the div where the text is displayed
                'jplayer_options'           : {},                    //additional jplayer options to initiate
                'jplayer_control_options'   : {},                    //options to send to the jplayer control generator
                'generate_jplayer_controls' : true,                  //flag indicating if controls should be generated or not
                'highlight_function'        : _textSegmentHighlight, //the function to use to highlight - requires object and text_segment_id as arguments
                'highlight_remove_function' : _textSegmentRemoveHighlight,  //function to remove highligh - requires object and text_segment_id as arguments
                'time_start_attribute'      : 'data-time_start',
                'time_end_attribute'        : 'data-time_end',
                'check_time_disabled'       : false
            }, options);

            //save options to the objects namespaced data holder
            var $this = $(this);
            var data = $this.data('mediaAlignedText');

            if(!data) {
                $this.data('mediaAlignedText', options);
            }

            //initialized jplayer controls
            if(options.generate_jplayer_controls) {
                if(options.media_files[0] != undefined && options.media_files[0].title != undefined) {
                    options.jplayer_control_options.title = options.media_files[0].title;
                }
                $('#'+options.jplayer_controls_id).html(_getJplayerContainerHtml(options.jplayer_control_options));
            }

            //initialize all the mappings and components
            _initTextSegments($this);
            _initJplayer($this);

        },

        //handles advancing to the aligned time of media when charGroup div clicked
        'textSegmentClicked' : function(text_segment_index) {

            var $this = $(this);
            var data = $this.data('mediaAlignedText');

            if( text_segment_index) {
                var text_segment = data.text_segments[text_segment_index];
                _setCurrentTextSegment($this, text_segment_index);

                if(text_segment.time_start >= 0) {
                    //go to the place in the media file
                    if($this.data('jPlayer').status.paused) { //if player paused just advance
                        $this.jPlayer('pause', text_segment.time_start/1000);
                    }
                    else { // if player not paused then go to
                        $this.jPlayer('play', text_segment.time_start/1000);
                    }
                }
            }
        },
        /**
         * play the media file for just the text segment
         * @param integer  text_segment_index   Index of the text_segment to be played
         */
        'playTextSegment' : function(text_segment_index) {
            var $this = $(this);
            var text_segment = $this.data('mediaAlignedText').text_segments[text_segment_index];
            $this.jPlayer('play', text_segment.time_start / 1000);

            //stop the jPlayer when segment is done
            var t = setTimeout("$('#" + $this.attr('id') + "').jPlayer('pause')", text_segment.time_end - text_segment.time_start);
        },

        'refreshSegments' : function() {
            var $this = $(this);
            //initialize all the mappings and components
            _initTextSegments($this);

        }
    };

    var _checkTimeChange = function($this){
        var data = $this.data('mediaAlignedText');

        //if checking time has been turned off then disable
        if(data.check_time_disabled) return false;

        var current_time = parseFloat($this.data("jPlayer").status.currentTime)*1000;
        var current_text_segment_invalid = false;

        //get current start and end times as a convenience
        if(data.current_text_segment_index == undefined) {
            var current_segment_start = -1;
            var current_segment_end = -1;
        }
        else {
            var segment = data.text_segments[data.current_text_segment_index];
            var current_segment_start = parseFloat(segment.time_start);
            var current_segment_end = parseFloat(segment.time_end);
        }

        //check if we are still in the timeframe of the currently selected text segment
        if(current_time >= current_segment_start && current_time <= current_segment_end) {
            return true;
        }
        else {    //unset the current text segment to remove highlight
            if(data.current_text_segment_index != undefined) {

                data.highlight_remove_function($this, data.current_text_segment_index);
                //unset the current_text_segment if necessary
                data.current_text_segment_index = undefined;
                current_text_segment_invalid = true;

            }

            //set up search for new text_segment starting from current segment and proceeding accordingly
            if(data.current_text_segment_index == undefined) {
                var start = 0;
                var step = 1;
                var end = data.text_segments.length -1;
            }
            else if (current_time > current_segment_end) {
                var start = data.current_text_segment_index;
                var step = 1;
                var end = data.text_segments.length -1;
            }
            else {
                var start = data.current_text_segment_index;
                var step = -1;
                var end = 0;
            }

            //loop through and search for the next matching segment
            for(i = start; i != end; i = i + step) {

                //set the text_segment var for convenience
                var text_segment = data.text_segments[i];
                if(text_segment.time_start < current_time && text_segment.time_end > current_time) {
                    _setCurrentTextSegment($this, i);
                    //check for the next match
                    return(true);
                }
                //check to see if we are seeking forwards and we've passed it already
                else if(step == 1 && text_segment.time_start > current_time){
                    break;
                }
                //check to see if we are seeking backwards and we've passed it already
                else if(step == -1 && text_segment.time_end < current_time) {
                    break;
                }
            }
        }
        //no match - must not have found one - save here to make sure current is unset
        $this.data('mediaAlignedText', data);
    };

    //initialize the Jplayer
    var _initJplayer = function ($this) {

        var data = $this.data('mediaAlignedText');

        //get the media types supplied by looking at the first media file
        var types_supplied = '';

        //check to see if the media file is defined
        if(data.media_files == undefined) {
            types_supplied = 'mp3';
        }
        else {
            for(i in data.media_files[0].media) {
                types_supplied = types_supplied + i + ',';
            }

            //load the media file
            data.jplayer_options.ready = function() {
                $(this).jPlayer("setMedia", data.media_files[0].media);
            };

            //knock off the last comma
            types_supplied = types_supplied.substring(0, types_supplied.length-1);
        }

        //initiate the jplayer
        data.jplayer_options = $.extend({
              swfPath: 'js',
              supplied: types_supplied,
              timeupdate: function() {_checkTimeChange($(this));}
        }, data.jplayer_options);

        $this.jPlayer(data.jplayer_options);
    };

    /**
     * initialize an ordered index of text segments
     * @todo it is assumed the text segments are listed in time order - should be checked
     */
    var _initTextSegments = function($this) {
        var data = $this.data('mediaAlignedText');
        var order = 0;
        var text_segments = new Array();

        $(data.text_viewer_css_selector + ' [' + data.time_start_attribute + ']').each(function(index) {
            var $this = $(this);
            $this.addClass('mat_text_segment');
            $this.attr('data-mat_segment', index);
            text_segments[index] = {'time_start': parseFloat($this.attr(data.time_start_attribute))};

            if($this.attr(data.time_end_attribute)) {
                text_segments[index].time_end = $this.attr(data.time_end_attribute);
            }

            //add end time to previous segment if not yet defined
            if(index > 0 && text_segments[index-1].time_end == undefined) {
                text_segments[index-1].time_end = parseFloat($this.attr(data.time_start_attribute));
            }
        });

        //set the time end for the last segment
        if(text_segments.length && text_segments[text_segments.length - 1].time_end == undefined) {
            //if time_start is undefined or -1 set to -1
            if(text_segments[text_segments.length - 1].time_start >= 0 && data.media_files[0].duration > 0) {
                text_segments[text_segments.length - 1].time_end = data.media_files[0].duration;
            }
            else { //set to the start time
                text_segments[text_segments.length -1].time_end = text_segments[text_segments.length -1].time_start;
            }
        }

        $(data.text_viewer_css_selector).on(
            'click.mediaAlignedText',
            '[' + data.time_start_attribute +']',
            {'parent' : $this},
            function(event) {
                event.data.parent.mediaAlignedText('textSegmentClicked', $(this).attr('data-mat_segment'));
        });
        data.text_segments = text_segments;

        //write back to the data object
        $this.data('mediaAlignedText', data);
    }

    /**
     * get the default html for the jplayer media player
     *
     * @param Object   options  The options that can be used to initialize the Jplayer Controller including the following
     * - jp_container_id:  The id of the container
     * - title:            The media file title to include in the player
     * -
     */
    var _getJplayerContainerHtml = function (options) {

            var options = $.extend({
                'jp_container_id' : 'jp_container_1',  //the id for the container for the controls
                'title'           : '',  //the title of the media file
                'media_type'      : 'audio'  //the media type for the container
            }, options);

            return '
\

\

\

\

play
\

pause
\

stop
\

mute
\

unmute
\

max volume
\

\

\

\

\

\

\

\

\

\

\

\

\

\

repeat
\

repeat off
\

\

\

\

\

\

'+options.title+'
\

\

\

\
              Update Required\
              To play the media you will need to either update your browser to a recent version or update your Flash plugin.\

\

\

';
    };

    /**
     * Set the current text segment to be the one supplied
     *
     * @param jQueryObject     $this   The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  Theindex of the textSegment to be highlighted
     */
    var _setCurrentTextSegment = function($this, text_segment_index) {
        var data = $this.data('mediaAlignedText');

        //if it is already set than do nothing
        if (data.current_text_segment_index == text_segment_index) {
            return true;
        }
        //if current text segment already set unhighlight it
        if (data.current_text_segment_index != undefined) {
            data.highlight_remove_function($this, data.current_text_segment_index);
        }

        //set new values for current and then highlight
        data.current_text_segment_index= text_segment_index;
        $this.data('mediaAlignedText', data);

        data.highlight_function($this, text_segment_index);

    };

    /**
     * Highlight a particular text segment
     *
     * @param jQueryObject     $this    The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  The index of the textSegment to be highlighted
     */
    var _textSegmentHighlight = function($this, text_segment_index) {
        $('[data-mat_segment='+text_segment_index+']').addClass('mat_highlighted_text_segment');
        $($this.data('mediaAlignedText').text_viewer_css_selector).scrollTo('.mat_highlighted_text_segment', 250, {'axis': 'y', 'offset': -20});
    };

    /**
     * Remove the highlight on a particular text segment
     *
     * @param jQueryObject     $this   The obect on which the mediaAlignedText has been instantiated
     * @param text_segment_index  integer  The id of the textSegment to have highlighting removed
     */
    var _textSegmentRemoveHighlight = function($this, text_segment_index){
        $('[data-mat_segment='+text_segment_index+']').removeClass('mat_highlighted_text_segment');
    };

})( jQuery );
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2013-2016 Memba Sarl. All rights reserved.
 * Sources at https://github.com/Memba
 */

/* jshint browser: true, jquery: true */
/* globals define: false */

(function (f, define) {
    'use strict';
    define([
        './vendor/markdown-it/markdown-it',
        './vendor/markdown-it/markdown-it-katex',
        './vendor/highlight/highlight.pack', // Keep at the top considering function parameters below
        './window.assert',
        './window.logger',
        './vendor/kendo/kendo.binder'
    ], f);
})(function (markdownit, katexRendered, highlight) {

    'use strict';

    (function ($, undefined) {

        var kendo = window.kendo;
        var Widget = kendo.ui.Widget;
        var MarkdownIt = window.markdownit || markdownit;
        var markdownItKatex = window.markdownItKatex || katexRendered;
        var hljs = window.hljs || highlight;
        var assert = window.assert;
        var logger = new window.Logger('kidoju.widgets.markdown');
        var STRING = 'string';
        var NULL = 'null';
        var UNDEFINED = 'undefined';
        var EMPTY = '';
        // var CHANGE = 'change';
        // var NS = '.kendoMarkdown';
        var WIDGET_CLASS = 'kj-markdown'; // 'k-widget kj-markdown';
        var RX_YML = /^---\n([\s\S]*)\n---/;
        var RX_KEYVAL = /([^:\n]+):([^\n]+)/g;
        var KEY_BLACKLIST = /[-\s]/g;
        var SCRIPT_SELECTOR = 'script[type="text/plain"]';
        var SCRIPT_TAG = '';
        var WRAP_TAG = '<wrap></wrap>';

        /*********************************************************************************
         * Helpers
         * See https://github.com/Memba/Memba-Blog/blob/master/webapp/lib/markdown.js#L60
         *********************************************************************************/

        /**
         * Return the yml metadata in value
         * @param content
         */
        function head(content) {
            var yml = {};
            var ymlMatches = content.match(RX_YML);
            if ($.isArray(ymlMatches) && ymlMatches.length > 1) {
                var keyvalMatches = ymlMatches[1].match(RX_KEYVAL);
                if ($.isArray(keyvalMatches) && keyvalMatches.length) {
                    for (var i = 0; i < keyvalMatches.length; i++) {
                        var keyval = keyvalMatches[i];
                        var pos = keyval.indexOf(':');
                        var key = keyval.substr(0, pos).trim().replace(KEY_BLACKLIST, '_');
                        var val = keyval.substr(pos + 1).trim();
                        yml[key] = val;
                    }
                }
            }
            return yml;
        }

        /**
         * Returns the markdown content in value
         * @param content
         */
        function body(content) {
            return content.replace(RX_YML, EMPTY).trim();
        }

        /*********************************************************************************
         * Widget
         *********************************************************************************/

        /**
         * Markdown
         * @class Markdown Widget (kendoMarkdown)
         */
        var Markdown = Widget.extend({

            /**
             * Initializes the widget
             * @param element
             * @param options
             */
            init: function (element, options) {
                var that = this;
                options = options || {};
                Widget.fn.init.call(that, element, options);
                logger.debug('widget initialized');
                that._initMarkdownIt();
                that._layout();
                // kendo.notify(that);
            },

            /**
             * Widget options
             * @property options
             */
            options: {
                name: 'Markdown',
                url: null,
                value: null
            },

            /**
             * Widget events
             * @property events
             */
            /*
             events: [
             CHANGE
             ],
             */

            /**
             * Value for MVVM binding
             * Returns either a JS function as a string or a library formula name prefixed as a Javascript comment
             * @param value
             */
            value: function (value) {
                var that = this;
                if ($.type(value) === STRING || $.type(value) === NULL) {
                    if (that._value !== value) {
                        that._value = value;
                        // that.trigger(CHANGE, { value: that._value });
                        that.refresh();
                    }
                } else if ($.type(value) === UNDEFINED) {
                    if ($.type(that._value) === STRING || $.type(that._value) === NULL) {
                        return that._value;
                    } else {
                        return undefined;
                    }
                } else {
                    throw new TypeError('`value` is expected to be a string if not undefined');
                }
            },

            /**
             * Initialize markdown-it
             * @private
             */
            _initMarkdownIt: function () {
                var that = this;

                // Initialize MarkdownIt
                that.md = new MarkdownIt({
                    html: false,
                    linkify: true,
                    typographer: true,
                    // See https://github.com/markdown-it/markdown-it#syntax-highlighting
                    highlight: function (code, lang) {
                        try {
                            return hljs.highlight(lang, code).value;
                        } catch (err) {
                            return hljs.highlightAuto(code).value;
                        }
                    }
                });

                // Initialize renderers
                that._initHljs();
                that._initLinkOpener();
                that._initImageRule();
                that._initKatex();
            },

            /**
             * Init Highligh.js
             * Adds hljs class to the pre tag
             * @see https://github.com/markdown-it/markdown-it/blob/88c6e0f8e6fd567c70ffabbc1e9ce7b980d2e3a9/support/demo_template/index.js#L94
             * @private
             */
            _initHljs: function () {
                var that = this;
                that.md.renderer.rules.fence = function (tokens, idx, options, env, self) {
                    var escapeHtml = that.md.utils.escapeHtml;
                    var unescapeAll = that.md.utils.unescapeAll;
                    var token = tokens[idx];
                    var info = token.info ? unescapeAll(token.info).trim() : '';
                    var langName = '';
                    var highlighted;
                    if (info) {
                        langName = info.split(/\s+/g)[0];
                        token.attrPush(['class', options.langPrefix + langName]);
                    }
                    if (options.highlight) {
                        highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);
                    } else {
                        highlighted = escapeHtml(token.content);
                    }
                    return '
' + highlighted  + '
\n';
                };
            },

            /**
             * Initialize link opener to open links in new window
             * @see https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
             * @private
             */
            _initLinkOpener: function () {
                var that = this;
                /* jscs: disable requireCamelCaseOrUpperCaseIdentifiers */
                var defaultRender = that.md.renderer.rules.link_open || function (tokens, idx, options, env, self) {
                        return self.renderToken(tokens, idx, options);
                    };
                that.md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
                    // If you are sure other plugins can't add `target` - drop check below
                    var targetIndex = tokens[idx].attrIndex('target');
                    if (targetIndex < 0) {
                        tokens[idx].attrPush(['target', '_blank']); // add new attribute
                    } else {
                        tokens[idx].attrs[targetIndex][1] = '_blank'; // replace value of existing attr
                    }
                    // same with rel - see https://mathiasbynens.github.io/rel-noopener/
                    var relIndex = tokens[idx].attrIndex('rel');
                    if (relIndex < 0) {
                        tokens[idx].attrPush(['rel', 'noopener']); // add new attribute
                    } else {
                        tokens[idx].attrs[relIndex][1] = 'noopener'; // replace value of existing attr
                    }
                    // pass token to default renderer.
                    return defaultRender(tokens, idx, options, env, self);
                };
                /* jscs: enable requireCamelCaseOrUpperCaseIdentifiers */
            },

            /**
             * Init the image rule
             * Adds the .img-responsive class to all images
             * @see https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md
             * @private
             */
            _initImageRule: function () {
                var that = this;
                var defaultRender = that.md.renderer.rules.image || function (tokens, idx, options, env, self) {
                        return self.renderToken(tokens, idx, options);
                    };
                that.md.renderer.rules.image = function (tokens, idx, options, env, slf) {
                    tokens[idx].attrPush(['class', 'img-responsive']);
                    return defaultRender(tokens, idx, options, env, slf);
                };
            },

            /**
             * Init Katex
             * @private
             */
            _initKatex: function () {
                if (markdownItKatex) {
                    this.md.use(markdownItKatex);
                }
            },

            /**
             * Builds the widget layout
             * @private
             */
            _layout: function () {
                var that = this;
                var options = that.options;
                that.wrapper = that.element;
                that.element.addClass(WIDGET_CLASS);
                if ($.type(options.url) === STRING) {
                    that.url(options.url);
                } else if ($.type(options.value) === STRING) {
                    that.value(options.value);
                } else {
                    that.inline();
                }
            },

            /**
             * Reads the markdown text in an inline script
             * @method _inline
             * @private
             */
            inline: function () {
                var that = this;
                var element = that.element;
                var inline = element.find(SCRIPT_SELECTOR);
                if (inline.length) {
                    that.value(inline.text());
                }
            },

            /**
             * Reads the markdown text from a url
             * @param url
             */
            url: function (url) {
                var that = this;
                if ($.type(url) === NULL) {
                    return;
                } else if ($.type(url) === STRING) {
                    $.get(url)
                        .done(function (data) {
                            that.value(data);
                        })
                        .fail(function () {
                            that.value(null);
                        });
                } else {
                    throw new TypeError('`url` is expected to be a string');
                }
            },

            /**
             * Returns yml metadata
             */
            metadata: function () {
                return head((this.value() || '').trim());
            },

            /**
             * Html displayed
             * @method html
             * @returns {*}
             */
            html: function () {
                var markdown = body((this.value() || '').trim());
                return this.md.render(markdown);
            },

            /**
             * Refresh the display (especially after changing the value)
             */
            refresh: function () {
                var that = this;
                var element = that.element;
                var inline = element.find(SCRIPT_SELECTOR);
                var script = EMPTY;
                if (inline.length) {
                    script = $(SCRIPT_TAG).text(inline.text()).wrapAll(WRAP_TAG).parent().html();
                }
                element.html(script + that.html());
            },

            /**
             * Clears the widget
             * @method _clear
             * @private
             */
            _clear: function () {
                var that = this;
                var element = that.element;
                // unbind kendo
                // kendo.unbind(element);
                // unbind all other events
                element.find('*').off();
                element.off();
                // remove descendants
                element.empty();
                // remove element classes
                element.removeClass(WIDGET_CLASS);
            },

            /**
             * Destroys the widget including all DOM modifications
             * @method destroy
             */
            destroy: function () {
                var that = this;
                Widget.fn.destroy.call(that);
                that._clear();
                kendo.destroy(that.element);
            }

        });

        kendo.ui.plugin(Markdown);

    }(window.jQuery));

    return window.kendo;

}, typeof define === 'function' && define.amd ? define : function (_, f) { 'use strict'; f(); });
<commit_msg>Added test for image class attribute<commit_after>/**
 * Copyright (c) 2013-2016 Memba Sarl. All rights reserved.
 * Sources at https://github.com/Memba
 */

/* jshint browser: true, jquery: true */
/* globals define: false */

(function (f, define) {
    'use strict';
    define([
        './vendor/markdown-it/markdown-it',
        './vendor/markdown-it/markdown-it-katex',
        './vendor/highlight/highlight.pack', // Keep at the top considering function parameters below
        './window.assert',
        './window.logger',
        './vendor/kendo/kendo.binder'
    ], f);
})(function (markdownit, katexRendered, highlight) {

    'use strict';

    (function ($, undefined) {

        var kendo = window.kendo;
        var Widget = kendo.ui.Widget;
        var MarkdownIt = window.markdownit || markdownit;
        var markdownItKatex = window.markdownItKatex || katexRendered;
        var hljs = window.hljs || highlight;
        var assert = window.assert;
        var logger = new window.Logger('kidoju.widgets.markdown');
        var STRING = 'string';
        var NULL = 'null';
        var UNDEFINED = 'undefined';
        var EMPTY = '';
        // var CHANGE = 'change';
        // var NS = '.kendoMarkdown';
        var WIDGET_CLASS = 'kj-markdown'; // 'k-widget kj-markdown';
        var RX_YML = /^---\n([\s\S]*)\n---/;
        var RX_KEYVAL = /([^:\n]+):([^\n]+)/g;
        var KEY_BLACKLIST = /[-\s]/g;
        var SCRIPT_SELECTOR = 'script[type="text/plain"]';
        var SCRIPT_TAG = '';
        var WRAP_TAG = '<wrap></wrap>';

        /*********************************************************************************
         * Helpers
         * See https://github.com/Memba/Memba-Blog/blob/master/webapp/lib/markdown.js#L60
         *********************************************************************************/

        /**
         * Return the yml metadata in value
         * @param content
         */
        function head(content) {
            var yml = {};
            var ymlMatches = content.match(RX_YML);
            if ($.isArray(ymlMatches) && ymlMatches.length > 1) {
                var keyvalMatches = ymlMatches[1].match(RX_KEYVAL);
                if ($.isArray(keyvalMatches) && keyvalMatches.length) {
                    for (var i = 0; i < keyvalMatches.length; i++) {
                        var keyval = keyvalMatches[i];
                        var pos = keyval.indexOf(':');
                        var key = keyval.substr(0, pos).trim().replace(KEY_BLACKLIST, '_');
                        var val = keyval.substr(pos + 1).trim();
                        yml[key] = val;
                    }
                }
            }
            return yml;
        }

        /**
         * Returns the markdown content in value
         * @param content
         */
        function body(content) {
            return content.replace(RX_YML, EMPTY).trim();
        }

        /*********************************************************************************
         * Widget
         *********************************************************************************/

        /**
         * Markdown
         * @class Markdown Widget (kendoMarkdown)
         */
        var Markdown = Widget.extend({

            /**
             * Initializes the widget
             * @param element
             * @param options
             */
            init: function (element, options) {
                var that = this;
                options = options || {};
                Widget.fn.init.call(that, element, options);
                logger.debug('widget initialized');
                that._initMarkdownIt();
                that._layout();
                // kendo.notify(that);
            },

            /**
             * Widget options
             * @property options
             */
            options: {
                name: 'Markdown',
                url: null,
                value: null
            },

            /**
             * Widget events
             * @property events
             */
            /*
             events: [
             CHANGE
             ],
             */

            /**
             * Value for MVVM binding
             * Returns either a JS function as a string or a library formula name prefixed as a Javascript comment
             * @param value
             */
            value: function (value) {
                var that = this;
                if ($.type(value) === STRING || $.type(value) === NULL) {
                    if (that._value !== value) {
                        that._value = value;
                        // that.trigger(CHANGE, { value: that._value });
                        that.refresh();
                    }
                } else if ($.type(value) === UNDEFINED) {
                    if ($.type(that._value) === STRING || $.type(that._value) === NULL) {
                        return that._value;
                    } else {
                        return undefined;
                    }
                } else {
                    throw new TypeError('`value` is expected to be a string if not undefined');
                }
            },

            /**
             * Initialize markdown-it
             * @private
             */
            _initMarkdownIt: function () {
                var that = this;

                // Initialize MarkdownIt
                that.md = new MarkdownIt({
                    html: false,
                    linkify: true,
                    typographer: true,
                    // See https://github.com/markdown-it/markdown-it#syntax-highlighting
                    highlight: function (code, lang) {
                        try {
                            return hljs.highlight(lang, code).value;
                        } catch (err) {
                            return hljs.highlightAuto(code).value;
                        }
                    }
                });

                // Initialize renderers
                that._initHljs();
                that._initLinkOpener();
                that._initImageRule();
                that._initKatex();
            },

            /**
             * Init Highligh.js
             * Adds hljs class to the pre tag
             * @see https://github.com/markdown-it/markdown-it/blob/88c6e0f8e6fd567c70ffabbc1e9ce7b980d2e3a9/support/demo_template/index.js#L94
             * @private
             */
            _initHljs: function () {
                var that = this;
                that.md.renderer.rules.fence = function (tokens, idx, options, env, self) {
                    var escapeHtml = that.md.utils.escapeHtml;
                    var unescapeAll = that.md.utils.unescapeAll;
                    var token = tokens[idx];
                    var info = token.info ? unescapeAll(token.info).trim() : '';
                    var langName = '';
                    var highlighted;
                    if (info) {
                        langName = info.split(/\s+/g)[0];
                        token.attrPush(['class', options.langPrefix + langName]);
                    }
                    if (options.highlight) {
                        highlighted = options.highlight(token.content, langName) || escapeHtml(token.content);
                    } else {
                        highlighted = escapeHtml(token.content);
                    }
                    return '
' + highlighted  + '
\n';
                };
            },

            /**
             * Initialize link opener to open links in new window
             * @see https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
             * @private
             */
            _initLinkOpener: function () {
                var that = this;
                /* jscs: disable requireCamelCaseOrUpperCaseIdentifiers */
                var defaultRender = that.md.renderer.rules.link_open || function (tokens, idx, options, env, self) {
                        return self.renderToken(tokens, idx, options);
                    };
                that.md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
                    // If you are sure other plugins can't add `target` - drop check below
                    var targetIndex = tokens[idx].attrIndex('target');
                    if (targetIndex < 0) {
                        tokens[idx].attrPush(['target', '_blank']); // add new attribute
                    } else {
                        tokens[idx].attrs[targetIndex][1] = '_blank'; // replace value of existing attr
                    }
                    // same with rel - see https://mathiasbynens.github.io/rel-noopener/
                    var relIndex = tokens[idx].attrIndex('rel');
                    if (relIndex < 0) {
                        tokens[idx].attrPush(['rel', 'noopener']); // add new attribute
                    } else {
                        tokens[idx].attrs[relIndex][1] = 'noopener'; // replace value of existing attr
                    }
                    // pass token to default renderer.
                    return defaultRender(tokens, idx, options, env, self);
                };
                /* jscs: enable requireCamelCaseOrUpperCaseIdentifiers */
            },

            /**
             * Init the image rule
             * Adds the .img-responsive class to all images
             * @see https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md
             * @private
             */
            _initImageRule: function () {
                var that = this;
                var defaultRender = that.md.renderer.rules.image || function (tokens, idx, options, env, self) {
                        return self.renderToken(tokens, idx, options);
                    };
                that.md.renderer.rules.image = function (tokens, idx, options, env, slf) {
                    var relIndex = tokens[idx].attrIndex('class');
                    // If you are sure other plugins can't add `class` - drop check below
                    if (relIndex < 0) {
                        tokens[idx].attrPush(['class', 'img-responsive']); // add new attribute
                    } else {
                        tokens[idx].attrs[relIndex][1] = 'img-responsive'; // replace value of existing attr
                    }
                    // pass token to default renderer.
                    return defaultRender(tokens, idx, options, env, slf);
                };
            },

            /**
             * Init Katex
             * @private
             */
            _initKatex: function () {
                if (markdownItKatex) {
                    this.md.use(markdownItKatex);
                }
            },

            /**
             * Builds the widget layout
             * @private
             */
            _layout: function () {
                var that = this;
                var options = that.options;
                that.wrapper = that.element;
                that.element.addClass(WIDGET_CLASS);
                if ($.type(options.url) === STRING) {
                    that.url(options.url);
                } else if ($.type(options.value) === STRING) {
                    that.value(options.value);
                } else {
                    that.inline();
                }
            },

            /**
             * Reads the markdown text in an inline script
             * @method _inline
             * @private
             */
            inline: function () {
                var that = this;
                var element = that.element;
                var inline = element.find(SCRIPT_SELECTOR);
                if (inline.length) {
                    that.value(inline.text());
                }
            },

            /**
             * Reads the markdown text from a url
             * @param url
             */
            url: function (url) {
                var that = this;
                if ($.type(url) === NULL) {
                    return;
                } else if ($.type(url) === STRING) {
                    $.get(url)
                        .done(function (data) {
                            that.value(data);
                        })
                        .fail(function () {
                            that.value(null);
                        });
                } else {
                    throw new TypeError('`url` is expected to be a string');
                }
            },

            /**
             * Returns yml metadata
             */
            metadata: function () {
                return head((this.value() || '').trim());
            },

            /**
             * Html displayed
             * @method html
             * @returns {*}
             */
            html: function () {
                var markdown = body((this.value() || '').trim());
                return this.md.render(markdown);
            },

            /**
             * Refresh the display (especially after changing the value)
             */
            refresh: function () {
                var that = this;
                var element = that.element;
                var inline = element.find(SCRIPT_SELECTOR);
                var script = EMPTY;
                if (inline.length) {
                    script = $(SCRIPT_TAG).text(inline.text()).wrapAll(WRAP_TAG).parent().html();
                }
                element.html(script + that.html());
            },

            /**
             * Clears the widget
             * @method _clear
             * @private
             */
            _clear: function () {
                var that = this;
                var element = that.element;
                // unbind kendo
                // kendo.unbind(element);
                // unbind all other events
                element.find('*').off();
                element.off();
                // remove descendants
                element.empty();
                // remove element classes
                element.removeClass(WIDGET_CLASS);
            },

            /**
             * Destroys the widget including all DOM modifications
             * @method destroy
             */
            destroy: function () {
                var that = this;
                Widget.fn.destroy.call(that);
                that._clear();
                kendo.destroy(that.element);
            }

        });

        kendo.ui.plugin(Markdown);

    }(window.jQuery));

    return window.kendo;

}, typeof define === 'function' && define.amd ? define : function (_, f) { 'use strict'; f(); });
<|endoftext|>


<commit_before>import React from 'react';
import { browserHistory } from 'react-router';
import { connect } from 'react-redux';

import config from '../config';
import actions from '../actions';
import BlockUser from '../components/components.block-user';

import { classNames } from '../utillities';

class UserList extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      search: false,
    };
  }

  render() {
    let users,
      input,
      loading;

    if (this.props.contactsState.isFetching) {
      loading = (

      );
    }

    users = this.props.users.map((user, index) => (
      <BlockUser
        key={ index }
        id={ user.id}
        name={ user.firstName }
        description={ user.headline }
        image={ user.image }
        lastSeenAt={ user.lastSeenAt }
        onClick={ () => this.selectUser(user) }
      />
    ));

    if (this.state.search) {
      input = (

            <button className="c-input-group__addon" onClick={ this.hideSearch.bind(this) }>

            </button>
            <input
              className="c-input-group__field"
              type="text"
              placeholder={ this.props.searchPlaceholder }
              autoFocus
              onKeyDown={ this.keyDownSearchInteraction.bind(this) }
              onKeyUp={ this.keyUpSearchInteraction.bind(this) }
            />
            <button className="c-input-group__addon submit"></button>

      );
    }

    return (

          { loading }

                  <button onClick={ this.showSearch.bind(this) }>

                  </button>

              { input }

          {!this.props.users.length &&
{ this.props.emptyMessage }
}

          { users }

    );
  }

  selectUser(user) {
    this.props.fetchPrivateChat(user);
  }

  showSearch() {
    console.info('Show search bar.');

    this.setState({
      search: true,
    });
  }

  hideSearch() {
    console.info('Hide search bar.');

    this.setState({
      search: false,
    });
  }

  keyDownSearchInteraction(e) {
    let value = e.target.value,
        keyCode = e.keyCode;

    // if input is empty and user presses backspace or esc, hide search.
    if (value === '' && keyCode === 8 || keyCode === 27) {
      this.hideSearch();
    }
  }

  keyUpSearchInteraction() {
    this.props.searchResults();
  }

  newGroupConversation() {
    this.props.swipeToContactPicker();
    browserHistory.push(`${config.urlPrefix}conversation`);
  }
}

UserList.propTypes = {
  openChat: React.PropTypes.func,
  users: React.PropTypes.array,
  searchResults: React.PropTypes.func,
};

const mapStateToProps = (state, ownProps) => state[config.stateName];

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    openChat: (id) => dispatch(actions.header.openChat(id)),
    swipeToContactPicker: () => dispatch(actions.swipeView.setSwipeViewIndex('conversationSwipeView', 1)),
    fetchPrivateChat: (user) => dispatch(actions.chats.fetchPrivateChat(user)),
  };
};

const UserListConnect = connect(mapStateToProps, mapDispatchToProps)(UserList);

export default UserListConnect;
<commit_msg>add swipeview to user-list<commit_after>import React from 'react';
import { browserHistory } from 'react-router';
import { connect } from 'react-redux';

import config from '../config';
import actions from '../actions';
import BlockUser from '../components/components.block-user';
import SwipeView from '../scopes/scopes.swipe-view';
import { classNames } from '../utillities';

class UserList extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      search: false,
    };
  }

  render() {
    let users,
      input,
      loading;

    if (this.props.contactsState.isFetching) {
      loading = (

      );
    }

    users = this.props.users.map((user, index) => (
      <BlockUser
        key={ index }
        id={ user.id}
        name={ user.firstName }
        description={ user.headline }
        image={ user.image }
        lastSeenAt={ user.lastSeenAt }
        onClick={ () => this.selectUser(user) }
      />
    ));

    if (this.state.search) {
      input = (

            <button className="c-input-group__addon" onClick={ this.hideSearch.bind(this) }>

            </button>
            <input
              className="c-input-group__field"
              type="text"
              placeholder={ this.props.searchPlaceholder }
              autoFocus
              onKeyDown={ this.keyDownSearchInteraction.bind(this) }
              onKeyUp={ this.keyUpSearchInteraction.bind(this) }
            />
            <button className="c-input-group__addon submit"></button>

      );
    }

    return (
      <SwipeView className="s-user-list">

          { loading }

                  <button onClick={ this.showSearch.bind(this) }>

                  </button>

              { input }

          {!this.props.users.length &&
{ this.props.emptyMessage }
}

          { users }

      </SwipeView>
    );
  }

  selectUser(user) {
    this.props.fetchPrivateChat(user);
  }

  showSearch() {
    console.info('Show search bar.');

    this.setState({
      search: true,
    });
  }

  hideSearch() {
    console.info('Hide search bar.');

    this.setState({
      search: false,
    });
  }

  keyDownSearchInteraction(e) {
    let value = e.target.value,
        keyCode = e.keyCode;

    // if input is empty and user presses backspace or esc, hide search.
    if (value === '' && keyCode === 8 || keyCode === 27) {
      this.hideSearch();
    }
  }

  keyUpSearchInteraction() {
    this.props.searchResults();
  }

  newGroupConversation() {
    this.props.swipeToContactPicker();
    browserHistory.push(`${config.urlPrefix}conversation`);
  }
}

UserList.propTypes = {
  openChat: React.PropTypes.func,
  users: React.PropTypes.array,
  searchResults: React.PropTypes.func,
};

const mapStateToProps = (state, ownProps) => state[config.stateName];

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    openChat: (id) => dispatch(actions.header.openChat(id)),
    swipeToContactPicker: () => dispatch(actions.swipeView.setSwipeViewIndex('conversationSwipeView', 1)),
    fetchPrivateChat: (user) => dispatch(actions.chats.fetchPrivateChat(user)),
  };
};

const UserListConnect = connect(mapStateToProps, mapDispatchToProps)(UserList);

export default UserListConnect;
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:SwellRTSession
 * @description
 * # SwellRTSession service
 * Provides an API to handle SwellRT sessions and network events
 */

angular.module('Pear2Pear')
  .factory(
    'SwellRTSession', ['$q', '$timeout', function($q, $timeout){

      var swellRTDef = $q.defer();
      var swellRTpromise = swellRTDef.promise;

      window.onSwellRTReadyCalled = false;

      window.onSwellRTReady = function(){
        swellRTDef.resolve();
        window.onSwellRTReadyCalled = true;
      };

      if (SwellRT && !window.onSwellRTReadyCalled){
        window.onSwellRTReady();
      }

      var sessionConnected = false;
      var dataSync = true;
      var lastDataSync;
      var connecting = false;

      // TODO use this to handle fatal exceptions
      var setFatalExceptionHandler = function(handler){
        swellRTpromise.then(function(){
          SwellRT.on(SwellRT.events.FATAL_EXCEPTION, function(){
            $timeout(function(){
              sessionConnected = false;
            });
            handler();
          });
        });
      };

      var sessionDef = $q.defer();
      var users = {
        password: '$password$',
        current: function() {
          return window.localStorage.getItem('userId');
        },
        setCurrent: function(name) {
          var cleanedName = name ? name.trim() : name;

          return window.localStorage.setItem('userId', cleanedName);
        },
        isCurrent: function(user) {
          return user === users.current();
        },
        loggedIn: function() {
          return users.current() != 'undefined' && users.current() !== null;
        },
        clearCurrent: function() {
          window.localStorage.removeItem('userId');
        }
      };

      var registerUser = function(userName, password, onSuccess, onError) {
        swellRTpromise.then(function(){
          SwellRT.registerUser(SwellRTConfig.server, userName, password, onSuccess, onError);
        });
      };

      var stopSession = function(){
        swellRTpromise.then(function(){
          SwellRT.stopSession();
          users.clearCurrent();
        });
      };

      swellRTpromise.then(function(){
        SwellRT.on(SwellRT.events.NETWORK_CONNECTED, function(){
          $timeout(function(){
            sessionConnected = true;
          });
        });

        SwellRT.on(SwellRT.events.NETWORK_DISCONNECTED, function(){
          $timeout(function(){
            sessionConnected = false;
          });
        });

        var dataStatusTimeout;

        SwellRT.on(SwellRT.events.DATA_STATUS_CHANGED, function(data){
          if (data.inFlightSize === 0 &&
              data.uncommittedSize === 0 &&
              data.unacknowledgedSize  === 0) {

            dataSync = true;
            lastDataSync = new Date();
            if (dataStatusTimeout){
              $timeout.cancel(dataStatusTimeout);
              dataStatusTimeout = undefined;
            }
          }
          else {
            if (!dataStatusTimeout){
              dataStatusTimeout = $timeout(function(){
                dataSync = false;
              }, 3000);
            }
          }
        });
      });
      // check variable connecting before calling startSession
      var startSession = function(userName, password, onSuccess, onError) {
        connecting = true;
        swellRTpromise.then(function(){
          if (sessionConnected) {
            if (userName && __session.address &&
                __session.address.split('@')[0] === userName.split('@')[0]) {
              return; // Session already started
            }
            // close other user's session
            else {
              stopSession();
            }
          }

          SwellRT.startSession(
            SwellRTConfig.server, userName || SwellRT.user.ANONYMOUS, password || '',
            function(){
              SwellRTConfig.swellrtServerDomain = __session.domain;
              if (userName){
                users.setCurrent(__session.address);
              } else {
                users.clearCurrent();
              }

              sessionDef.resolve(SwellRT);
              onSuccess();

              sessionConnected = true;
              connecting = false;
            }, function() {
              onError();
              connecting = false;
            });
        });
      };

      var autoStartSession = function(){
        var user, pass;

        if (users.current() !== null) {
          user = users.current();
          pass = users.password;
        }

        startSession(
          user, pass, function(){
            $timeout(
              function() {
              });
          },
          function(error) {
            console.log(error);
          });
      };

      return {
        users: users,
        registerUser: registerUser,
        startSession: startSession,
        stopSession: stopSession,
        setFatalExceptionHandler: setFatalExceptionHandler,
        // TODO no restart session without user saying so
        // TODO stop session before start session after a timeout
        // TODO if reconnected, get again all objects
        status: {
          isConnected: function(){
            return sessionConnected;
          },
          isDataSync: function(){
            return dataSync;
          },
          lastDataSync: function(){
            return lastDataSync;
          }
        },

        // TODO refactor with Prototype version of proxy objects to avoid the use of onLoad
        onLoad: function(f) {
          if (!sessionConnected && !connecting){
            autoStartSession();
          }
          sessionDef.promise.then(f);
        }
      };
    }]);
<commit_msg>fix SwellRT existance condition<commit_after>'use strict';

/**
 * @ngdoc function
 * @name Pear2Pear.service:SwellRTSession
 * @description
 * # SwellRTSession service
 * Provides an API to handle SwellRT sessions and network events
 */

angular.module('Pear2Pear')
  .factory(
    'SwellRTSession', ['$q', '$timeout', function($q, $timeout){

      var swellRTDef = $q.defer();
      var swellRTpromise = swellRTDef.promise;

      window.onSwellRTReadyCalled = false;

      window.onSwellRTReady = function(){
        swellRTDef.resolve();
        window.onSwellRTReadyCalled = true;
      };

      if (window.SwellRT && !window.onSwellRTReadyCalled){
        window.onSwellRTReady();
      }

      var sessionConnected = false;
      var dataSync = true;
      var lastDataSync;
      var connecting = false;

      // TODO use this to handle fatal exceptions
      var setFatalExceptionHandler = function(handler){
        swellRTpromise.then(function(){
          SwellRT.on(SwellRT.events.FATAL_EXCEPTION, function(){
            $timeout(function(){
              sessionConnected = false;
            });
            handler();
          });
        });
      };

      var sessionDef = $q.defer();
      var users = {
        password: '$password$',
        current: function() {
          return window.localStorage.getItem('userId');
        },
        setCurrent: function(name) {
          var cleanedName = name ? name.trim() : name;

          return window.localStorage.setItem('userId', cleanedName);
        },
        isCurrent: function(user) {
          return user === users.current();
        },
        loggedIn: function() {
          return users.current() != 'undefined' && users.current() !== null;
        },
        clearCurrent: function() {
          window.localStorage.removeItem('userId');
        }
      };

      var registerUser = function(userName, password, onSuccess, onError) {
        swellRTpromise.then(function(){
          SwellRT.registerUser(SwellRTConfig.server, userName, password, onSuccess, onError);
        });
      };

      var stopSession = function(){
        swellRTpromise.then(function(){
          SwellRT.stopSession();
          users.clearCurrent();
        });
      };

      swellRTpromise.then(function(){
        SwellRT.on(SwellRT.events.NETWORK_CONNECTED, function(){
          $timeout(function(){
            sessionConnected = true;
          });
        });

        SwellRT.on(SwellRT.events.NETWORK_DISCONNECTED, function(){
          $timeout(function(){
            sessionConnected = false;
          });
        });

        var dataStatusTimeout;

        SwellRT.on(SwellRT.events.DATA_STATUS_CHANGED, function(data){
          if (data.inFlightSize === 0 &&
              data.uncommittedSize === 0 &&
              data.unacknowledgedSize  === 0) {

            dataSync = true;
            lastDataSync = new Date();
            if (dataStatusTimeout){
              $timeout.cancel(dataStatusTimeout);
              dataStatusTimeout = undefined;
            }
          }
          else {
            if (!dataStatusTimeout){
              dataStatusTimeout = $timeout(function(){
                dataSync = false;
              }, 3000);
            }
          }
        });
      });
      // check variable connecting before calling startSession
      var startSession = function(userName, password, onSuccess, onError) {
        connecting = true;
        swellRTpromise.then(function(){
          if (sessionConnected) {
            if (userName && __session.address &&
                __session.address.split('@')[0] === userName.split('@')[0]) {
              return; // Session already started
            }
            // close other user's session
            else {
              stopSession();
            }
          }

          SwellRT.startSession(
            SwellRTConfig.server, userName || SwellRT.user.ANONYMOUS, password || '',
            function(){
              SwellRTConfig.swellrtServerDomain = __session.domain;
              if (userName){
                users.setCurrent(__session.address);
              } else {
                users.clearCurrent();
              }

              sessionDef.resolve(SwellRT);
              onSuccess();

              sessionConnected = true;
              connecting = false;
            }, function() {
              onError();
              connecting = false;
            });
        });
      };

      var autoStartSession = function(){
        var user, pass;

        if (users.current() !== null) {
          user = users.current();
          pass = users.password;
        }

        startSession(
          user, pass, function(){
            $timeout(
              function() {
              });
          },
          function(error) {
            console.log(error);
          });
      };

      return {
        users: users,
        registerUser: registerUser,
        startSession: startSession,
        stopSession: stopSession,
        setFatalExceptionHandler: setFatalExceptionHandler,
        // TODO no restart session without user saying so
        // TODO stop session before start session after a timeout
        // TODO if reconnected, get again all objects
        status: {
          isConnected: function(){
            return sessionConnected;
          },
          isDataSync: function(){
            return dataSync;
          },
          lastDataSync: function(){
            return lastDataSync;
          }
        },

        // TODO refactor with Prototype version of proxy objects to avoid the use of onLoad
        onLoad: function(f) {
          if (!sessionConnected && !connecting){
            autoStartSession();
          }
          sessionDef.promise.then(f);
        }
      };
    }]);
<|endoftext|>


<commit_before>//-------------------------------------------------------------------------------
// Imports
//-------------------------------------------------------------------------------

import {
    Class,
    DataUtil,
    Obj,
    Promises,
    Throwables
} from 'bugcore';
import semver from 'semver';
import { RecipeQueryCache } from '../caches';
import { QueryResultData } from '../data';
import {
    RecipeVersionsInfoManager
} from '../managers';

//-------------------------------------------------------------------------------
// Declare Class
//-------------------------------------------------------------------------------

/**
 * @class
 * @extends {Obj}
 */
const RecipeQueryStore = Class.extend(Obj, {

    _name: 'recipe.RecipeQueryStore',

    //-------------------------------------------------------------------------------
    // Constructor
    //-------------------------------------------------------------------------------

    /**
     * @constructs
     */
    _constructor() {

        this._super();

        //-------------------------------------------------------------------------------
        // Private Properties
        //-------------------------------------------------------------------------------

        //TODO BRN: Add watchers for when recipeQuerys change. On change, delete cache entry (or reload file and update cache)
        /**
         * @private
         * @type {RecipeQueryCache}
         */
        this.recipeQueryCache        = new RecipeQueryCache();
    },

    //-------------------------------------------------------------------------------
    // Getters and Setters
    //-------------------------------------------------------------------------------

    /**
     * @return {RecipeQueryCache}
     */
    getRecipeQueryCache() {
        return this.recipeQueryCache;
    },

    //-------------------------------------------------------------------------------
    // Public Methods
    //-------------------------------------------------------------------------------

    /**
     * @param {string} recipeQuery
     * @return {Promise<QueryResultData>}
     */
    query(recipeQuery) {
        const queryResultData = this.recipeQueryCache.get(recipeQuery);
        if (!queryResultData) {
            return this.doQuery(recipeQuery)
                .then((newQueryResultData) => {
                    this.recipeQueryCache.set(recipeQuery, newQueryResultData);
                    return newQueryResultData;
                });
        }
        return Promises.resolve(queryResultData);
    },

    //-------------------------------------------------------------------------------
    // Private Methods
    //-------------------------------------------------------------------------------

    /**
     * @private
     * @param {string} recipeQuery
     * @returns {Promise.<QueryResultData>}
     */
    doQuery(recipeQuery) {
        return Promises.try(() => {
            const recipeQueryData = this.parseRecipeQuery(recipeQuery);
            return this.loadRecipeVersionsInfoEntity(recipeQueryData.name)
                .then((recipeVersionsInfoEntity) => {
                    if (!recipeVersionsInfoEntity) {
                        throw Throwables.exception('RecipeDoesNotExist', {}, 'A recipe by the name "' + recipeQueryData.name + '" does not exist.');
                    }
                    return [recipeQueryData, recipeVersionsInfoEntity];
                });
        }).then((results) => {
            const [recipeQueryData, recipeVersionsInfoEntity] = results;
            const versionNumber = this.resolveRecipeVersionNumber(recipeQueryData, recipeVersionsInfoEntity);
            console.log('recipeQueryData.name:', recipeQueryData.name, ' versionNumber:', versionNumber);

            if (!versionNumber) {
                throw Throwables.exception('NoVersionMatch', {}, 'Cannot find a version match for "' + recipeQuery + '"');
            }
            return new QueryResultData({
                name: recipeQueryData.name,
                versionNumber
            });
        });
    },

    /**
     * @private
     * @param {string} recipeName
     * @return {Promise<RecipeVersionsInfoEntity>}
     */
    loadRecipeVersionsInfoEntity(recipeName) {
        return RecipeVersionsInfoManager.get({
            recipeName,
            recipeScope: 'public',
            recipeType: 'gulp'
        });
    },

    /**
     * @private
     * @param {string} recipeQuery
     * @return {{
     *      name: string,
     *      versionQuery: string
     * }}
     */
    parseRecipeQuery(recipeQuery) {
        if (recipeQuery.indexOf('@') > -1) {
            const parts = recipeQuery.split('@');
            return {
                name: parse[0],
                versionQuery: parts[1]
            };
        }
        return {
            name: recipeQuery,
            versionQuery: ''
        };
    },

    /**
     * @private
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveLastPublishedRecipe(recipeVersionsInfoEntity) {
        return recipeVersionsInfoEntity.getLast();
    },

    /**
     * @private
     * @param {{
     *      name: string,
     *      versionQuery: string
     * }} recipeQueryData
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveRecipeVersionNumber(recipeQueryData, recipeVersionsInfoEntity) {
        if (recipeQueryData.versionQuery) {
            return this.resolveRecipeVersionQuery(recipeQueryData.versionQuery, recipeVersionsInfoEntity);
        }
        return this.resolveLastPublishedRecipe(recipeVersionsInfoEntity);
    },

    /**
     * @private
     * @param {string} versionQuery
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveRecipeVersionQuery(versionQuery, recipeVersionsInfoEntity) {
        const allVersions = DataUtil.map(recipeVersionsInfoEntity.getAll() || {}, version => version);
        return semver.maxSatisfying(allVersions, versionQuery);
    }
});

//-------------------------------------------------------------------------------
// Exports
//-------------------------------------------------------------------------------

export default RecipeQueryStore;
<commit_msg>Fix typo<commit_after>//-------------------------------------------------------------------------------
// Imports
//-------------------------------------------------------------------------------

import {
    Class,
    DataUtil,
    Obj,
    Promises,
    Throwables
} from 'bugcore';
import semver from 'semver';
import { RecipeQueryCache } from '../caches';
import { QueryResultData } from '../data';
import {
    RecipeVersionsInfoManager
} from '../managers';

//-------------------------------------------------------------------------------
// Declare Class
//-------------------------------------------------------------------------------

/**
 * @class
 * @extends {Obj}
 */
const RecipeQueryStore = Class.extend(Obj, {

    _name: 'recipe.RecipeQueryStore',

    //-------------------------------------------------------------------------------
    // Constructor
    //-------------------------------------------------------------------------------

    /**
     * @constructs
     */
    _constructor() {

        this._super();

        //-------------------------------------------------------------------------------
        // Private Properties
        //-------------------------------------------------------------------------------

        //TODO BRN: Add watchers for when recipeQuerys change. On change, delete cache entry (or reload file and update cache)
        /**
         * @private
         * @type {RecipeQueryCache}
         */
        this.recipeQueryCache        = new RecipeQueryCache();
    },

    //-------------------------------------------------------------------------------
    // Getters and Setters
    //-------------------------------------------------------------------------------

    /**
     * @return {RecipeQueryCache}
     */
    getRecipeQueryCache() {
        return this.recipeQueryCache;
    },

    //-------------------------------------------------------------------------------
    // Public Methods
    //-------------------------------------------------------------------------------

    /**
     * @param {string} recipeQuery
     * @return {Promise<QueryResultData>}
     */
    query(recipeQuery) {
        const queryResultData = this.recipeQueryCache.get(recipeQuery);
        if (!queryResultData) {
            return this.doQuery(recipeQuery)
                .then((newQueryResultData) => {
                    this.recipeQueryCache.set(recipeQuery, newQueryResultData);
                    return newQueryResultData;
                });
        }
        return Promises.resolve(queryResultData);
    },

    //-------------------------------------------------------------------------------
    // Private Methods
    //-------------------------------------------------------------------------------

    /**
     * @private
     * @param {string} recipeQuery
     * @returns {Promise.<QueryResultData>}
     */
    doQuery(recipeQuery) {
        return Promises.try(() => {
            const recipeQueryData = this.parseRecipeQuery(recipeQuery);
            return this.loadRecipeVersionsInfoEntity(recipeQueryData.name)
                .then((recipeVersionsInfoEntity) => {
                    if (!recipeVersionsInfoEntity) {
                        throw Throwables.exception('RecipeDoesNotExist', {}, 'A recipe by the name "' + recipeQueryData.name + '" does not exist.');
                    }
                    return [recipeQueryData, recipeVersionsInfoEntity];
                });
        }).then((results) => {
            const [recipeQueryData, recipeVersionsInfoEntity] = results;
            const versionNumber = this.resolveRecipeVersionNumber(recipeQueryData, recipeVersionsInfoEntity);
            console.log('recipeQueryData.name:', recipeQueryData.name, ' versionNumber:', versionNumber);

            if (!versionNumber) {
                throw Throwables.exception('NoVersionMatch', {}, 'Cannot find a version match for "' + recipeQuery + '"');
            }
            return new QueryResultData({
                name: recipeQueryData.name,
                versionNumber
            });
        });
    },

    /**
     * @private
     * @param {string} recipeName
     * @return {Promise<RecipeVersionsInfoEntity>}
     */
    loadRecipeVersionsInfoEntity(recipeName) {
        return RecipeVersionsInfoManager.get({
            recipeName,
            recipeScope: 'public',
            recipeType: 'gulp'
        });
    },

    /**
     * @private
     * @param {string} recipeQuery
     * @return {{
     *      name: string,
     *      versionQuery: string
     * }}
     */
    parseRecipeQuery(recipeQuery) {
        if (recipeQuery.indexOf('@') > -1) {
            const parts = recipeQuery.split('@');
            return {
                name: parts[0],
                versionQuery: parts[1]
            };
        }
        return {
            name: recipeQuery,
            versionQuery: ''
        };
    },

    /**
     * @private
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveLastPublishedRecipe(recipeVersionsInfoEntity) {
        return recipeVersionsInfoEntity.getLast();
    },

    /**
     * @private
     * @param {{
     *      name: string,
     *      versionQuery: string
     * }} recipeQueryData
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveRecipeVersionNumber(recipeQueryData, recipeVersionsInfoEntity) {
        if (recipeQueryData.versionQuery) {
            return this.resolveRecipeVersionQuery(recipeQueryData.versionQuery, recipeVersionsInfoEntity);
        }
        return this.resolveLastPublishedRecipe(recipeVersionsInfoEntity);
    },

    /**
     * @private
     * @param {string} versionQuery
     * @param {RecipeVersionsInfoEntity} recipeVersionsInfoEntity
     * @return {string}
     */
    resolveRecipeVersionQuery(versionQuery, recipeVersionsInfoEntity) {
        const allVersions = DataUtil.map(recipeVersionsInfoEntity.getAll() || {}, version => version);
        return semver.maxSatisfying(allVersions, versionQuery);
    }
});

//-------------------------------------------------------------------------------
// Exports
//-------------------------------------------------------------------------------

export default RecipeQueryStore;
<|endoftext|>


<commit_before>import * as utils from '../../../utils';
import router from '../../../router';
import * as helper from '../../helper';
import { header as headerWidget, backButton } from '../../shared/common';
import layout from '../../layout';
import * as gameApi from '../../../lichess/game';
import i18n from '../../../i18n';
import gameStatus from '../../../lichess/status';
import { toggleGameBookmark } from '../../../xhr';
import session from '../../../session';
import * as m from 'mithril';
import ViewOnlyBoard from '../../shared/ViewOnlyBoard';

export default function view(vnode) {
  const ctrl = this;
  const username = vnode.attrs.id;

  const header = headerWidget.bind(undefined, null,
    backButton(username + ' games')
  );

  function renderBody() {
    return (

          <select id="filterGames" onchange={ctrl.onFilterChange}>
            {ctrl.availableFilters().map(f => {
              return (
                <option value={f.key} selected={ctrl.currentFilter() === f.key}>
                  {utils.capitalize(i18n(f.label).replace('%s ', ''))} ({f.count})
                </option>
              );
            })}
          </select>

        {renderAllGames(ctrl)}

    );
  }

  return layout.free(header, renderBody);
}

function renderAllGames(ctrl) {
  return (

        { ctrl.games().map((g, i) => renderGame(ctrl, g, i, ctrl.userId)) }
        {ctrl.isLoadingNextPage() ?

loading...
 : null
        }

  );
}

function bookmarkAction(ctrl, id, index) {
  const longAction = () => window.plugins.toast.show(i18n('bookmarkThisGame'), 'short', 'top');
  return helper.ontapY(() => {
    toggleGameBookmark(id)
    .then(() => {
      ctrl.toggleBookmark(index);
    })
    .catch(utils.handleXhrError);
  }, longAction);
}

function renderGame(ctrl, g, index, userId) {
  const wideScreenOrLandscape = helper.isWideScreen() || helper.isLandscape();
  const time = gameApi.time(g);
  const mode = g.rated ? i18n('rated') : i18n('casual');
  const title = time + ' • ' + g.variant.name + ' • ' + mode;
  const date = window.moment(g.timestamp).calendar();
  const status = gameStatus.toLabel(g.status.name, g.winner, g.variant.key) +
    (g.winner ? '. ' + i18n(g.winner === 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') + '.' : '');
  const icon = utils.gameIcon(g.perf) || '';
  const userColor = g.players.white.userId === userId ? 'white' : 'black';
  const evenOrOdd = index % 2 === 0 ? 'even' : 'odd';
  const star = g.bookmarked ? 't' : 's';
  const link = g.winner ? () => router.set(`/analyse/online/${g.id}/${userColor}`) : () => router.set(`/game/${g.id}/${userColor}`);

  return (
    <li className={`list_item userGame ${evenOrOdd}`} key={g.id}>
      { session.isConnected() ?
        <button className="iconStar" data-icon={star} oncreate={bookmarkAction(ctrl, g.id, index)} /> : null
      }

        {wideScreenOrLandscape ? m(ViewOnlyBoard, {fen: g.fen, lastMove: g.lastMove, orientation: userColor }) : null}

{title}

          {date}

            {renderPlayer(g.players, 'white', g.variant.key)}

            {renderPlayer(g.players, 'black', g.variant.key)}

{status}

          {g.opening ?

{g.opening.name}
 : null
          }
          {g.analysed ?

            Computer analysis available

 : null
          }

  );
}

function renderPlayer(players, color, variant) {
  let player = players[color];
  let playerName;
  if (player.userId) playerName = player.userId;
  else if (player.aiLevel) {
    player.ai = player.aiLevel;
    player.engineName = variant === 'crazyhouse' ? 'Sunsetter' : 'Stockfish';
    playerName = utils.aiName(player);
  }
  else playerName = 'Anonymous';

  return (

      {playerName}

      {player.rating ?
      {player.rating}{player.conditional && '?'} : null
      }

  );
}
<commit_msg>Fix analyse link<commit_after>import * as m from 'mithril';
import * as utils from '../../../utils';
import router from '../../../router';
import * as helper from '../../helper';
import { header as headerWidget, backButton } from '../../shared/common';
import layout from '../../layout';
import * as gameApi from '../../../lichess/game';
import i18n from '../../../i18n';
import gameStatus from '../../../lichess/status';
import { toggleGameBookmark } from '../../../xhr';
import session from '../../../session';
import ViewOnlyBoard from '../../shared/ViewOnlyBoard';

export default function view(vnode) {
  const ctrl = this;
  const username = vnode.attrs.id;

  const header = headerWidget.bind(undefined, null,
    backButton(username + ' games')
  );

  function renderBody() {
    return (

          <select id="filterGames" onchange={ctrl.onFilterChange}>
            {ctrl.availableFilters().map(f => {
              return (
                <option value={f.key} selected={ctrl.currentFilter() === f.key}>
                  {utils.capitalize(i18n(f.label).replace('%s ', ''))} ({f.count})
                </option>
              );
            })}
          </select>

        {renderAllGames(ctrl)}

    );
  }

  return layout.free(header, renderBody);
}

function renderAllGames(ctrl) {
  return (

        { ctrl.games().map((g, i) => renderGame(ctrl, g, i, ctrl.userId)) }
        {ctrl.isLoadingNextPage() ?

loading...
 : null
        }

  );
}

function bookmarkAction(ctrl, id, index) {
  const longAction = () => window.plugins.toast.show(i18n('bookmarkThisGame'), 'short', 'top');
  return helper.ontapY(() => {
    toggleGameBookmark(id)
    .then(() => {
      ctrl.toggleBookmark(index);
    })
    .catch(utils.handleXhrError);
  }, longAction);
}

function renderGame(ctrl, g, index, userId) {
  const wideScreenOrLandscape = helper.isWideScreen() || helper.isLandscape();
  const time = gameApi.time(g);
  const mode = g.rated ? i18n('rated') : i18n('casual');
  const title = time + ' • ' + g.variant.name + ' • ' + mode;
  const date = window.moment(g.timestamp).calendar();
  const status = gameStatus.toLabel(g.status.name, g.winner, g.variant.key) +
    (g.winner ? '. ' + i18n(g.winner === 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') + '.' : '');
  const icon = utils.gameIcon(g.perf) || '';
  const userColor = g.players.white.userId === userId ? 'white' : 'black';
  const evenOrOdd = index % 2 === 0 ? 'even' : 'odd';
  const star = g.bookmarked ? 't' : 's';
  const link = g.status.id < gameStatus.ids.aborted ?
    () => router.set(`/game/${g.id}/${userColor}`) :
    () => router.set(`/analyse/online/${g.id}/${userColor}`);

  return (
    <li className={`list_item userGame ${evenOrOdd}`} key={g.id}>
      { session.isConnected() ?
        <button className="iconStar" data-icon={star} oncreate={bookmarkAction(ctrl, g.id, index)} /> : null
      }

        {wideScreenOrLandscape ? m(ViewOnlyBoard, {fen: g.fen, lastMove: g.lastMove, orientation: userColor }) : null}

{title}

          {date}

            {renderPlayer(g.players, 'white', g.variant.key)}

            {renderPlayer(g.players, 'black', g.variant.key)}

{status}

          {g.opening ?

{g.opening.name}
 : null
          }
          {g.analysed ?

            Computer analysis available

 : null
          }

  );
}

function renderPlayer(players, color, variant) {
  let player = players[color];
  let playerName;
  if (player.userId) playerName = player.userId;
  else if (player.aiLevel) {
    player.ai = player.aiLevel;
    player.engineName = variant === 'crazyhouse' ? 'Sunsetter' : 'Stockfish';
    playerName = utils.aiName(player);
  }
  else playerName = 'Anonymous';

  return (

      {playerName}

      {player.rating ?
      {player.rating}{player.conditional && '?'} : null
      }

  );
}
<|endoftext|>