<commit_before>	/**
	 * Widget
	 * Conf, API, UI, Init
	 */
	var WidgetConf = {
		// url:  'http://localhost:8084/widget/',
		url:  'http://193.27.9.220/widget/',
		app:  'ul',
		user: '4',
		id:   $(location).attr('pathname'),
		isVendor: true
	};

	var WidgetAPI = {
		doRequest: function(controller, preferences, callback){
			$.ajax({
				url: WidgetConf.url + controller,
				type: 'get',
				data: preferences,
				dataType: 'json',
				async: true,
				crossDomain: true,
				xhrFields: {
					withCredentials: false
				},
				success: function(data){
					callback(data);
				},
				error: function(xhr, msg){
					console.log(msg);
				}
			});
		},
		getAverageAndComments: function(callback){
			var preferences = {
				'id':WidgetConf.id,
				'app':WidgetConf.app,
				'user':WidgetConf.user,
				'grvalue':'0'
			};
			WidgetAPI.doRequest('ControllerGetStar', preferences, callback);
		},
		setRateAndComment: function(rate, title, comment, callback){
			var preferences = {
				'id':WidgetConf.id,
				'app':WidgetConf.app,
				'user':WidgetConf.user,
				'star':rate,
				'titlecomment': title,
				'comment': comment
			};
			WidgetAPI.doRequest('ControllerStar', preferences, callback);
		},
		deleteRateAndComment: function(callback){
			var preferences = {
				'id':WidgetConf.id,
				'app':WidgetConf.app,
				'user':WidgetConf.user
			};
			WidgetAPI.doRequest('ControllerDeleteStar', preferences, callback);
		},
		setCommentLike: function(IdComment, callback){
			var preferences = {
				'IdComment':IdComment,
				'user':WidgetConf.user
			};
			WidgetAPI.doRequest('ControllerRateComment', preferences, callback);
		},
		setReplyToComment: function(IdComment, comment, callback){
			var preferences = {
				'IdComment':IdComment,
				'user':WidgetConf.user,
				'comment': comment
			};
			WidgetAPI.doRequest('ControllerReplyComment', preferences, callback);
		}
	};

	var WidgetUI = {

		init:true,
		activeRate : '0',
		timer:null,

		initWidget: function(){
			WidgetUI.setWidgetStateWithRate('0');

			$('#morecomments').on('click', function(){
				WidgetUI.moreComments();
				return false;
			});

			$('#buttonRate').on('click', function(){
				WidgetUI.provideRate();
				return false;
			});

			$('#buttonBack, #buttonOk').on('click', function(){
				WidgetUI.resetWidget();
				return false;
			});

			$('#buttonSend').on('click', function(){
				WidgetUI.addRateAndComment();
				return false;
			});

			$('#buttonDelete').on('click', function(){
				WidgetUI.deleteRate();
				return false;
			});

			$('#rate, #histogram').on('mouseover', function(){
				WidgetUI.showHistogram();
			}).on('mouseout', function(){
				WidgetUI.hideHistogram();
			});

			$('#rate, #histogram a').on('focus', function(){
				WidgetUI.showHistogram();
			}).on('blur', function(){
				WidgetUI.hideHistogram();
			});

			$('#histogram table a').on('click', function(){
				var rate = $(this).parents('tr').index() + 1;
				WidgetUI.setWidgetStateWithRate(rate);
				return false;
			});

			/* New interface  ¿Integration? */
			$('#widget_stars_rate_part #rate, #widget_stars_comments_part #histogram').off('mouseover mouseout focus blur').find('a').off('focus blur');

			//Event delegation
			$('#listComments').on('click', 'a.like_button', function(){
				var IdComment = $(this).data('IdComment');
				WidgetUI.likeComment(IdComment);
				return false;
			});
			$('#listComments').on('click', 'a.reply_button', function(){
				var IdComment = $(this).data('IdComment');
				WidgetUI.replyComment(IdComment);
				return false;
			});
			$('#listComments').on('click', '.cancel', function(){
				WidgetUI.removeReplyForm();
				return false;
			});
			$('#listComments').on('click', '.send', function(){
				WidgetUI.addReplyToComment();
				return false;
			});

		},
		setWidgetStateWithRate: function(rate){
			this.activeRate = rate;
			WidgetAPI.getAverageAndComments(WidgetUI.setWidgetStateCallback);
		},
		setWidgetStateCallback: function(data){
			$("#valuemedia").text(data.value);
			var stars_rate = '';
			for(var i = 1; i <= 5; i++){
				stars_rate += (i <= data.value) ? '★' : '☆';
			}
			$("#valuemedia_stars").html(stars_rate);
			$("#widget_comments_ul").empty();
			WidgetUI.setAddEditComment(false);
			$("#morecomments").show();
			if(data.comments.length){
				var n = 0;
				$.each(data.comments, function(i){
					if(WidgetUI.activeRate == '0' || this.value == WidgetUI.activeRate){
						n++;
						if(this.user == WidgetConf.user){
							WidgetUI.setAddEditComment(true);
						}
						WidgetUI.addComment(this);
					}
				});

				if(n == 0){
					WidgetUI.setNoComments();
				}
				if(n == 1){
					$("#morecomments").hide();
				}
			}
			else{
				WidgetUI.setNoComments();
			}
			if(WidgetUI.init){
				WidgetUI.init = false;
				WidgetUI.moreComments();
			}
			WidgetUI.setHistogram(data.comments);
		},
		addRateAndComment: function(){
			var rate = $('input[name=widget_stars_rate]:checked').val();
			if(typeof rate !== 'undefined'){
				var title = $('#widget_title_comment').val();
				var comment = $('#widget_comment').val();
				WidgetAPI.setRateAndComment(rate, title, comment, WidgetUI.addRateAndCommentCallback);
			}
			else{
				/*@todo:set errors on widget itself, don't use an alert */
				alert('You must select a rating from 1 to 5 stars');
			}
		},
		addRateAndCommentCallback: function(){
			WidgetUI.setWidgetStateWithRate('0');
			$("#provideoyourrate").hide();
			$("#provideoyourrateok").show();
			$("#provideoyourrateok").focus();
		},
		moreComments: function(){
			if($('#listComments > ul > li').eq(1).is(':hidden')){
				$("#morecomments").text("Less Comments").removeClass('more').addClass('less');
				$('#listComments > ul > li').show();
			}
			else{
				$("#morecomments").text("More Comments").removeClass('less').addClass('more');
				$('#listComments > ul > li:not(:first-child)').hide();
			}
			$('#buttonprovideoyourrate').show();
		},
		provideRate: function(){
			$('#buttonprovideoyourrate').hide();
			$('#provideoyourrate').show();
			$('#listComments').hide();
			$('#morecomments').hide();
			$("#widget_title_comment").val('');
			$("#widget_comment").val('');
			$("#widget_stars_rate_1").focus();
		},
		editRate: function(){
			$('#buttonprovideoyourrate').hide();
			$('#provideoyourrate').show();
			$('#listComments').hide();
			$('#morecomments').hide();
			var title = $('#listComments li.user-' + WidgetConf.user + ' strong span').text();
			$("#widget_title_comment").val(title);
			var comment = $('#listComments li.user-' + WidgetConf.user + ' > div').text();
			$("#widget_comment").val(comment);
			$("#widget_stars_rate_1").focus();
		},
		deleteRate: function(){
			WidgetAPI.deleteRateAndComment(WidgetUI.deleteRateCallback);
		},
		deleteRateCallback: function(){
			WidgetUI.setWidgetStateWithRate('0');
		},
		resetWidget: function(){
			$('#buttonprovideoyourrate').show();
			$('#listComments').show();
			$('#provideoyourrate').hide();
			$('#provideoyourrateok').hide();
			if($('#listComments li').length > 1){
				$('#morecomments').show();
			}
		},
		showHistogram: function(){
			clearTimeout(WidgetUI.timer);
			$('#histogram').removeClass('visuallyhidden');
		},
		hideHistogram: function(){
			WidgetUI.timer = setTimeout(function(){
				$('#histogram').addClass('visuallyhidden');
			}, 500);
		},
		setHistogram: function(data){
			var histogram = [
				{votes:0, percent:0},
				{votes:0, percent:0},
				{votes:0, percent:0},
				{votes:0, percent:0},
				{votes:0, percent:0}
			];
			for(var i = 0; i < data.length; i++){
				histogram[data[i].value - 1].votes++;
				histogram[data[i].value - 1].percent = histogram[data[i].value - 1].votes * 100 / data.length;
			}
			for(var i = 0; i < histogram.length; i++){
				$('#histogram table tr').eq(i).find('td').html(
					$('').css({width: histogram[i].percent + '%'}).text(histogram[i].percent.toFixed(2) + '%')
				);
			}
		},
		likeComment: function(IdComment){
			WidgetAPI.setCommentLike(IdComment, WidgetUI.likeCommentCallback);
		},
		likeCommentCallback: function(data){
			WidgetUI.setWidgetStateWithRate('0');
		},
		setAddEditComment: function(edit){
			if(edit){
				$('#buttonRate').off('click').text('Edit your comment and rate').on('click', function(){
					WidgetUI.editRate();
					return false;
				});
				$('#buttonDelete').show();
			}
			else{
				$('#buttonRate').off('click').text('Write a comment and rate it').on('click', function(){
					WidgetUI.provideRate();
					return false;
				});
				$('#buttonDelete').hide();
			}
		},
		setNoComments: function(){
			var li = $('
').html('There are no comments with this rate yet');
			$("#widget_comments_ul").append(li);
			$("#morecomments").hide();
		},
		addComment: function(comment){
			var img = '';
			var title = '' + comment.title + ' (' + comment.value + '/5)';
			var date = '&#128197 ' + new Date(comment.date).toLocaleDateString('en-UK') + '';
			var body = '
' + comment.c + '
';
			var html = img + title + '
' + date + '
' + body;
			var li = $('
').html(html).addClass('user-' + comment.user).addClass('comment-' + comment.id).attr({tabIndex:0});
			if(!comment.userComment){
				var like = $('❤').data({IdComment:comment.id});
			}
			else{
				var like = $('❤').data({IdComment:comment.id});
			}

			li.append(like);
			var likes = $('').text(comment.rate);
			li.append(likes);
			//Vendor reply
			if(WidgetConf.isVendor){
				var reply = $('').attr({href:'#'}).text('Reply comment').addClass('reply_button').data({IdComment:comment.id});
				li.append(reply);
			}
			//Replies
			if(comment.replies.length > 0){
				var ul_replies = $('
');
				$.each(comment.replies, function(){
					var li_reply = $('
');
					var reply_date = $('').text(new Date(this.date).toLocaleDateString('en-UK')).addClass('date');
					var reply_text = $('
').text(this.reply);
					li_reply.append(reply_date);
					li_reply.append(reply_text);
					ul_replies.append(li_reply);
				});
				li.append(ul_replies);
			}
			$("#widget_comments_ul").append(li);
		},
		replyComment: function(IdComment){
			WidgetUI.addReplyForm(IdComment);
		},
		addReplyForm: function(IdComment){
			WidgetUI.removeReplyForm();
			var form = $('<form>');
			var textarea = $('<textarea>');
			var comment = $('<input>').attr({type:'hidden'}).val(IdComment);
			var send = $('<button>').text('Send').addClass('send blue_button');
			var back = $('<button>').text('Cancel').addClass('cancel red_button');
			form.append(comment);
			form.append(textarea);
			form.append(back);
			form.append(send);
			$('#listComments li.comment-' + IdComment + ' .reply_button').after(form);
		},
		removeReplyForm: function(){
			$('#listComments li form').remove();
		},
		addReplyToComment: function(){
			var IdComment = $('#listComments li form input[type=hidden]').val();
			var comment = $('#listComments li form textarea').val();
			WidgetAPI.setReplyToComment(IdComment, comment, WidgetUI.addReplyToCommentCallback);
		},
		addReplyToCommentCallback: function(){
			//WidgetUI.removeReplyForm();//¿needed?
			WidgetUI.setWidgetStateWithRate('0');
		}
	};

	$(document).ready(function(){
		WidgetUI.initWidget();
	});

	/**
	 * Unified Listing Interface
	 * Select User
	 */
	function loadusermenu(){
		$('#user-menu').toggle();
	}

	function selectuser(usercode){
		if(usercode === 1){
			$('#username').html('Nacho');
			WidgetConf.user = '1';
		}else if(usercode === 2){
			$('#username').html('Esteban');
			WidgetConf.user = '2';
		}else if(usercode === 3){
			$('#username').html('Manuel');
			WidgetConf.user = '3';
		}else if(usercode === 4){
			$('#username').html('Pablo');
			WidgetConf.user = '4';
		}
		$('#user-menu').toggle();
		WidgetUI.resetWidget();
		WidgetUI.setWidgetStateWithRate('0');
	}<commit_msg>UL-93: fix wording, js side<commit_after>    /**
     * Widget
     * Conf, API, UI, Init
     */
    var WidgetConf = {
        // url:  'http://localhost:8084/widget/',
        url:  'http://193.27.9.220/widget/',
        app:  'ul',
        user: '4',
        id:   $(location).attr('pathname'),
        isVendor: true,
        strings : widgetStrings.value,
        currentLocale : widgetStrings.locale
    };

    var WidgetAPI = {
        doRequest: function(controller, preferences, callback){
            $.ajax({
                url: WidgetConf.url + controller,
                type: 'get',
                data: preferences,
                dataType: 'json',
                async: true,
                crossDomain: true,
                xhrFields: {
                    withCredentials: false
                },
                success: function(data){
                    callback(data);
                },
                error: function(xhr, msg){
                    console.log(msg);
                }
            });
        },
        getAverageAndComments: function(callback){
            var preferences = {
                'id':WidgetConf.id,
                'app':WidgetConf.app,
                'user':WidgetConf.user,
                'grvalue':'0'
            };
            WidgetAPI.doRequest('ControllerGetStar', preferences, callback);
        },
        setRateAndComment: function(rate, title, comment, callback){
            var preferences = {
                'id':WidgetConf.id,
                'app':WidgetConf.app,
                'user':WidgetConf.user,
                'star':rate,
                'titlecomment': title,
                'comment': comment
            };
            WidgetAPI.doRequest('ControllerStar', preferences, callback);
        },
        deleteRateAndComment: function(callback){
            var preferences = {
                'id':WidgetConf.id,
                'app':WidgetConf.app,
                'user':WidgetConf.user
            };
            WidgetAPI.doRequest('ControllerDeleteStar', preferences, callback);
        },
        setCommentLike: function(IdComment, callback){
            var preferences = {
                'IdComment':IdComment,
                'user':WidgetConf.user
            };
            WidgetAPI.doRequest('ControllerRateComment', preferences, callback);
        },
        setReplyToComment: function(IdComment, comment, callback){
            var preferences = {
                'IdComment':IdComment,
                'user':WidgetConf.user,
                'comment': comment
            };
            WidgetAPI.doRequest('ControllerReplyComment', preferences, callback);
        }
    };

    var WidgetUI = {

        init:true,
        activeRate : '0',
        timer:null,

        initWidget: function(){
            WidgetUI.setWidgetStateWithRate('0');

            $('#morecomments').on('click', function(){
                WidgetUI.moreComments();
                return false;
            });

            $('#buttonRate').on('click', function(){
                WidgetUI.provideRate();
                return false;
            });

            $('#buttonBack, #buttonOk').on('click', function(){
                WidgetUI.resetWidget();
                return false;
            });

            $('#buttonSend').on('click', function(){
                WidgetUI.addRateAndComment();
                return false;
            });

            $('#buttonDelete').on('click', function(){
                WidgetUI.deleteRate();
                return false;
            });

            $('#rate, #histogram').on('mouseover', function(){
                WidgetUI.showHistogram();
            }).on('mouseout', function(){
                WidgetUI.hideHistogram();
            });

            $('#rate, #histogram a').on('focus', function(){
                WidgetUI.showHistogram();
            }).on('blur', function(){
                WidgetUI.hideHistogram();
            });

            $('#histogram table a').on('click', function(){
                var rate = $(this).parents('tr').index() + 1;
                WidgetUI.setWidgetStateWithRate(rate);
                return false;
            });

            /* New interface  ¿Integration? */
            $('#widget_stars_rate_part #rate, #widget_stars_comments_part #histogram').off('mouseover mouseout focus blur').find('a').off('focus blur');

            //Event delegation
            $('#listComments').on('click', 'a.like_button', function(){
                var IdComment = $(this).data('IdComment');
                WidgetUI.likeComment(IdComment);
                return false;
            });
            $('#listComments').on('click', 'a.reply_button', function(){
                var IdComment = $(this).data('IdComment');
                WidgetUI.replyComment(IdComment);
                return false;
            });
            $('#listComments').on('click', '.cancel', function(){
                WidgetUI.removeReplyForm();
                return false;
            });
            $('#listComments').on('click', '.send', function(){
                WidgetUI.addReplyToComment();
                return false;
            });

        },
        setWidgetStateWithRate: function(rate){
            this.activeRate = rate;
            WidgetAPI.getAverageAndComments(WidgetUI.setWidgetStateCallback);
        },
        setWidgetStateCallback: function(data){
            $("#valuemedia").text(data.value);
            var stars_rate = '';
            for(var i = 1; i <= 5; i++){
                stars_rate += (i <= data.value) ? '★' : '☆';
            }
            $("#valuemedia_stars").html(stars_rate);
            $("#widget_comments_ul").empty();
            WidgetUI.setAddEditComment(false);
            $("#morecomments").show();
            if(data.comments.length){
                var n = 0;
                $.each(data.comments, function(i){
                    if(WidgetUI.activeRate == '0' || this.value == WidgetUI.activeRate){
                        n++;
                        if(this.user == WidgetConf.user){
                            WidgetUI.setAddEditComment(true);
                        }
                        WidgetUI.addComment(this);
                    }
                });

                if(n == 0){
                    WidgetUI.setNoComments();
                }
                if(n == 1){
                    $("#morecomments").hide();
                }
            }
            else{
                WidgetUI.setNoComments();
            }
            if(WidgetUI.init){
                WidgetUI.init = false;
                WidgetUI.moreComments();
            }
            WidgetUI.setHistogram(data.comments);
        },
        addRateAndComment: function(){
            var rate = $('input[name=widget_stars_rate]:checked').val();
            if(typeof rate !== 'undefined'){
                var title = $('#widget_title_comment').val();
                var comment = $('#widget_comment').val();
                WidgetAPI.setRateAndComment(rate, title, comment, WidgetUI.addRateAndCommentCallback);
            }
            else{
                /*@todo:set errors on widget itself, don't use an alert */
                alert('You must select a rating from 1 to 5 stars');
            }
        },
        addRateAndCommentCallback: function(){
            WidgetUI.setWidgetStateWithRate('0');
            $("#provideoyourrate").hide();
            $("#provideoyourrateok").show();
            $("#provideoyourrateok").focus();
        },
        moreComments: function(){
            if($('#listComments > ul > li').eq(1).is(':hidden')){
                $("#morecomments").text(WidgetConf.strings("LESS COMMENTS")).removeClass('more').addClass('less');
                $('#listComments > ul > li').show();
            }
            else{
                $("#morecomments").text(WidgetConf.strings("MORE COMMENTS")).removeClass('less').addClass('more');
                $('#listComments > ul > li:not(:first-child)').hide();
            }
            $('#buttonprovideoyourrate').show();
        },
        provideRate: function(){
            $('#buttonprovideoyourrate').hide();
            $('#provideoyourrate').show();
            $('#listComments').hide();
            $('#morecomments').hide();
            $("#widget_title_comment").val('');
            $("#widget_comment").val('');
            $("#widget_stars_rate_1").focus();
        },
        editRate: function(){
            $('#buttonprovideoyourrate').hide();
            $('#provideoyourrate').show();
            $('#listComments').hide();
            $('#morecomments').hide();
            var title = $('#listComments li.user-' + WidgetConf.user + ' strong span').text();
            $("#widget_title_comment").val(title);
            var comment = $('#listComments li.user-' + WidgetConf.user + ' > div').text();
            $("#widget_comment").val(comment);
            $("#widget_stars_rate_1").focus();
        },
        deleteRate: function(){
            WidgetAPI.deleteRateAndComment(WidgetUI.deleteRateCallback);
        },
        deleteRateCallback: function(){
            WidgetUI.setWidgetStateWithRate('0');
        },
        resetWidget: function(){
            $('#buttonprovideoyourrate').show();
            $('#listComments').show();
            $('#provideoyourrate').hide();
            $('#provideoyourrateok').hide();
            if($('#listComments li').length > 1){
                $('#morecomments').show();
            }
        },
        showHistogram: function(){
            clearTimeout(WidgetUI.timer);
            $('#histogram').removeClass('visuallyhidden');
        },
        hideHistogram: function(){
            WidgetUI.timer = setTimeout(function(){
                $('#histogram').addClass('visuallyhidden');
            }, 500);
        },
        setHistogram: function(data){
            var histogram = [
                {votes:0, percent:0},
                {votes:0, percent:0},
                {votes:0, percent:0},
                {votes:0, percent:0},
                {votes:0, percent:0}
            ];
            for(var i = 0; i < data.length; i++){
                histogram[data[i].value - 1].votes++;
                histogram[data[i].value - 1].percent = histogram[data[i].value - 1].votes * 100 / data.length;
            }
            for(var i = 0; i < histogram.length; i++){
                $('#histogram table tr').eq(i).find('td').html(
                    $('').css({width: histogram[i].percent + '%'}).text(histogram[i].percent.toFixed(2) + '%')
                );
            }
        },
        likeComment: function(IdComment){
            WidgetAPI.setCommentLike(IdComment, WidgetUI.likeCommentCallback);
        },
        likeCommentCallback: function(data){
            WidgetUI.setWidgetStateWithRate('0');
        },
        setAddEditComment: function(edit){
            if(edit){
                $('#buttonRate').off('click').text(WidgetConf.strings('UPDATE')).on('click', function(){
                    WidgetUI.editRate();
                    return false;
                });
                $('#buttonDelete').show();
            }
            else{
                $('#buttonRate').off('click').text(WidgetConf.strings('CREATE')).on('click', function(){
                    WidgetUI.provideRate();
                    return false;
                });
                $('#buttonDelete').hide();
            }
        },
        setNoComments: function(){
            var li = $('
').html('There are no comments with this rate yet');
            $("#widget_comments_ul").append(li);
            $("#morecomments").hide();
        },
        addComment: function(comment){
            var img = '';
            var title = '' + comment.title + ' (' + comment.value + '/5)';
            var date = '&#128197 ' + new Date(comment.date).toLocaleDateString(WidgetConf.currentLocale()) + '';
            var body = '
' + comment.c + '
';
            var html = img + title + '
' + date + '
' + body;
            var li = $('
').html(html).addClass('user-' + comment.user).addClass('comment-' + comment.id).attr({tabIndex:0});
            if(!comment.userComment){
                var like = $('❤').data({IdComment:comment.id});
            }
            else{
                var like = $('❤').data({IdComment:comment.id});
            }

            li.append(like);
            var likes = $('').text(comment.rate);
            li.append(likes);
            //Vendor reply
            if(WidgetConf.isVendor){
                var reply = $('').attr({href:'#'}).text(WidgetConf.strings('REPLY')).addClass('reply_button').data({IdComment:comment.id});
                li.append(reply);
            }
            //Replies
            if(comment.replies.length > 0){
                var ul_replies = $('
');
                $.each(comment.replies, function(){
                    var li_reply = $('
');
                    var reply_date = $('').text(new Date(this.date).toLocaleDateString(WidgetConf.currentLocale())).addClass('date');
                    var reply_text = $('
').text(this.reply);

                    li_reply.append(reply_date);
                    li_reply.append(reply_text);
                    ul_replies.append(li_reply);
                });
                li.append(ul_replies);
            }
            $("#widget_comments_ul").append(li);
        },
        replyComment: function(IdComment){
            WidgetUI.addReplyForm(IdComment);
        },
        addReplyForm: function(IdComment){
            WidgetUI.removeReplyForm();
            var form = $('<form>');
            var textarea = $('<textarea>');
            var comment = $('<input>').attr({type:'hidden'}).val(IdComment);
            var send = $('<button>').text(WidgetConf.strings("SEND")).addClass('send blue_button');
            var back = $('<button>').text(WidgetConf.strings("CANCEL")).addClass('cancel red_button');
            form.append(comment);
            form.append(textarea);
            form.append(back);
            form.append(send);
            $('#listComments li.comment-' + IdComment + ' .reply_button').after(form);
        },
        removeReplyForm: function(){
            $('#listComments li form').remove();
        },
        addReplyToComment: function(){
            var IdComment = $('#listComments li form input[type=hidden]').val();
            var comment = $('#listComments li form textarea').val();
            WidgetAPI.setReplyToComment(IdComment, comment, WidgetUI.addReplyToCommentCallback);
        },
        addReplyToCommentCallback: function(){
            //WidgetUI.removeReplyForm();//¿needed?
            WidgetUI.setWidgetStateWithRate('0');
        }
    };

    $(document).ready(function(){
        WidgetUI.initWidget();
    });

    /**
     * Unified Listing Interface
     * Select User
     */
    function loadusermenu(){
        $('#user-menu').toggle();
    }

    function selectuser(usercode){
        if(usercode === 1){
            $('#username').html('Nacho');
            WidgetConf.user = '1';
        }else if(usercode === 2){
            $('#username').html('Esteban');
            WidgetConf.user = '2';
        }else if(usercode === 3){
            $('#username').html('Manuel');
            WidgetConf.user = '3';
        }else if(usercode === 4){
            $('#username').html('Pablo');
            WidgetConf.user = '4';
        }
        $('#user-menu').toggle();
        WidgetUI.resetWidget();
        WidgetUI.setWidgetStateWithRate('0');
    }<|endoftext|>


<commit_before>module.exports = function(config) {
    var karmaConfig = {
        basePath: '..',
        browsers: ['PhantomJS'],
        frameworks: ['browserify', 'jasmine'],
        reporters: ['progress', 'coverage'],
        preprocessors: {
            'test/unit/**/*.js': ['browserify'],
            '_site/*.html': ['html2js']
        },
        coverageReporter: {
            dir : 'test/coverage/',
            reporters: [
                { type: 'html',
                    subdir: function(browser) {
                        return browser.toLowerCase().split(/[ /-]/)[0];
                    },
                    watermarks: {
                        statements: [75, 85],
                        lines: [75, 85],
                        functions: [75, 85],
                        branches:[75, 85]
                    }},
                { type: 'json-summary', subdir: '.', file: 'summary.json' },
            ]
        },
        files: [
            {pattern: '_site/**/vendor.*', included: true, served: true, watched: true},
            {pattern: '_site/**/*.*', included: true, served: true, watched: true},
            'test/unit/**/*.spec.js'
        ],
        exclude: [
            '**/*.png',
            '**/*.min.js'
        ]
    };
    var pkg = require('../package.json');
    karmaConfig.browser = pkg.browser || {};
    karmaConfig["browserify-shim"] = pkg["browserify-shim"] || {};
    karmaConfig.browserify = pkg.browserify || {};
    if (karmaConfig.browserify.transform) {
        karmaConfig.browserify.transform.push('browserify-istanbul');
    } else {
        karmaConfig.browserify.transform = [ 'browserify-istanbul' ];
    }
    return config.set(karmaConfig);
};<commit_msg>disable coverage<commit_after>module.exports = function(config) {
    var karmaConfig = {
        basePath: '..',
        browsers: ['PhantomJS'],
        frameworks: ['browserify', 'jasmine'],
        reporters: ['progress'],
        preprocessors: {
            'test/unit/**/*.js': ['browserify'],
            '_site/*.html': ['html2js']
        },
        files: [
            {pattern: '_site/**/vendor.*', included: true, served: true, watched: true},
            {pattern: '_site/**/*.*', included: true, served: true, watched: true},
            'test/unit/**/*.spec.js'
        ],
        exclude: [
            '**/*.png',
            '**/*.min.js'
        ]
    };
    var pkg = require('../package.json');
    karmaConfig.browser = pkg.browser || {};
    karmaConfig["browserify-shim"] = pkg["browserify-shim"] || {};
    karmaConfig.browserify = pkg.browserify || {};
    if (karmaConfig.browserify.transform) {
        karmaConfig.browserify.transform.push('browserify-istanbul');
    } else {
        karmaConfig.browserify.transform = [ 'browserify-istanbul' ];
    }
    return config.set(karmaConfig);
};
<|endoftext|>


<commit_before>"use strict";
{
  const sg = require('./lib.js');
  const navigable = new Set([
    'A',
    'BUTTON'
  ]);
  const test = {
    run
  };

  module.exports = test;

  Object.assign( self, { test } );

  // one possible test
    function validate( must_find, found, must_not_find ) {
      found = new Set(found);
      const must_find_ok = [...must_find].every( el => found.has( el ) );
      const must_not_find_ok = [...must_not_find].every( el => ! found.has(el) );
      const okay = must_find_ok && must_not_find_ok;
      return okay;
    }

  // helpers
    function inc( el ) {
      const n = parseFloat( el.innerText );
      el.innerText = n + 1;
    }

    function zero( el ) {
      el.innerText = 0;
    }

  function run() {
    console.log("Running tests...");
    const neg_set = new Set();
    const set = new Set();
    let found = [];
    document.addEventListener('click', e => {
      if ( e.target.id == 'negate' ) {
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
      } else {
        if ( navigable.has( e.target.tagName ) ) {
          e.preventDefault();
        }
      }
    });
    document.addEventListener('mouseup', e => {
      if( e.target.id == 'generalize' ) {
        const {positive,negative} = sg.generalize( [...set], [...neg_set] );
        generalized_selector.innerText = `${positive} !(${negative})` || 'n/a';
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
        found = Array.from( document.querySelectorAll(positive) );
        if ( !!negative ) {
          const remove = new Set( Array.from( document.querySelectorAll(negative) ) );
          found = found.filter( el => ! remove.has( el ) );
        }
        found_count.innerText = found.length;
        const result = validate( set, found, neg_set );
        console.log(" Test result?", result );
        console.log( "\npos", positive, "\nneg", negative, "\npset", set, "\nnset", neg_set, "\nfound", found );
        Array.from( set ).forEach( el => {
          el.style.outline = "3px dashed lime";
        });
        Array.from( neg_set ).forEach( el => {
          el.style.outline = "3px dashed red";
        });
        found.forEach( el => {
          el.style.filter = "sepia(1)";
          el.style.background = "lime";
        });
      } else if ( e.target.id == 'clear' ) {
        Array.from( set ).forEach( el => {
          el.style.outline = "none";
        });
        Array.from( neg_set ).forEach( el => {
          el.style.outline = "none";
        });
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
        neg_set.clear();
        set.clear();
        negate.checked = false;
        found_count.innerText = 'n/a';
        zero( positive_example_count );
        zero( negative_example_count );
        generalized_selector.innerText = 'n/a';
      } else {
        if ( !( e.target instanceof HTMLElement ) || e.target.matches( 'article#testcontrols, article#testcontrols *' ) ) {
          return;
        }
        if ( negate.checked ) {
          if ( set.has( e.target ) ) {
            set.delete( e.target );
          }
          neg_set.add( e.target );
          inc( negative_example_count );
        } else {
          if ( neg_set.has( e.target ) ) {
            neg_set.delete( e.target );
          }
          set.add( e.target );
          inc( positive_example_count );
        }
        if ( e.target.style ) {
          if ( negate.checked ) {
              e.target.style.outline = "2px solid red";
          } else {
              e.target.style.outline = "2px solid lime";
          }
        }
      }
    });
  }
}
<commit_msg>Fixed incorrect example count, closes #11<commit_after>"use strict";
{
  const sg = require('./lib.js');
  const navigable = new Set([
    'A',
    'BUTTON'
  ]);
  const test = {
    run
  };

  module.exports = test;

  Object.assign( self, { test } );

  // one possible test
    function validate( must_find, found, must_not_find ) {
      found = new Set(found);
      const must_find_ok = [...must_find].every( el => found.has( el ) );
      const must_not_find_ok = [...must_not_find].every( el => ! found.has(el) );
      const okay = must_find_ok && must_not_find_ok;
      return okay;
    }

  // helpers
    function inc( el ) {
      const n = parseFloat( el.innerText );
      el.innerText = n + 1;
    }

    function zero( el ) {
      el.innerText = 0;
    }

  function run() {
    console.log("Running tests...");
    const neg_set = new Set();
    const set = new Set();
    let found = [];
    document.addEventListener('click', e => {
      if ( e.target.id == 'negate' ) {
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
      } else {
        if ( navigable.has( e.target.tagName ) ) {
          e.preventDefault();
        }
      }
    });
    document.addEventListener('mouseup', e => {
      if( e.target.id == 'generalize' ) {
        const {positive,negative} = sg.generalize( [...set], [...neg_set] );
        generalized_selector.innerText = `${positive} !(${negative})` || 'n/a';
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
        found = Array.from( document.querySelectorAll(positive) );
        if ( !!negative ) {
          const remove = new Set( Array.from( document.querySelectorAll(negative) ) );
          found = found.filter( el => ! remove.has( el ) );
        }
        found_count.innerText = found.length;
        const result = validate( set, found, neg_set );
        console.log(" Test result?", result );
        console.log( "\npos", positive, "\nneg", negative, "\npset", set, "\nnset", neg_set, "\nfound", found );
        Array.from( set ).forEach( el => {
          el.style.outline = "3px dashed lime";
        });
        Array.from( neg_set ).forEach( el => {
          el.style.outline = "3px dashed red";
        });
        found.forEach( el => {
          el.style.filter = "sepia(1)";
          el.style.background = "lime";
        });
      } else if ( e.target.id == 'clear' ) {
        Array.from( set ).forEach( el => {
          el.style.outline = "none";
        });
        Array.from( neg_set ).forEach( el => {
          el.style.outline = "none";
        });
        found.forEach( el => {
          el.style.filter = "none";
          el.style.background = "none";
        });
        neg_set.clear();
        set.clear();
        negate.checked = false;
        found_count.innerText = 'n/a';
        zero( positive_example_count );
        zero( negative_example_count );
        generalized_selector.innerText = 'n/a';
      } else {
        if ( !( e.target instanceof HTMLElement ) || e.target.matches( 'article#testcontrols, article#testcontrols *' ) ) {
          return;
        }
        if ( negate.checked ) {
          if ( set.has( e.target ) ) {
            set.delete( e.target );
          }
          if ( ! neg_set.has( e.target ) ) {
            neg_set.add( e.target );
            inc( negative_example_count );
          }
        } else {
          if ( neg_set.has( e.target ) ) {
            neg_set.delete( e.target );
          }
          if ( ! set.has( e.target ) ) {
            set.add( e.target );
            inc( positive_example_count );
          }
        }
        if ( e.target.style ) {
          if ( negate.checked ) {
              e.target.style.outline = "2px solid red";
          } else {
              e.target.style.outline = "2px solid lime";
          }
        }
      }
    });
  }
}
<|endoftext|>


<commit_before>import {expect} from 'chai';
import {getLabel, defaultLabel} from '../src/label';

describe('label', () => {

	describe('getLabel', () => {

		it('should return default if given nothing', () => {
			expect(getLabel(null, null)).to.eq(defaultLabel);
      expect(getLabel(null, null)).to.have.length.above(0);
		});

	});

});
<commit_msg>silly string formatting<commit_after>import {expect} from 'chai';
import {getLabel, defaultLabel} from '../src/label';

describe('label', () => {

	describe('getLabel', () => {

		it('should return default if given nothing', () => {
			expect(getLabel(null, null)).to.eq(defaultLabel);
			expect(getLabel(null, null)).to.have.length.above(0);
		});

	});

});
<|endoftext|>


<commit_before>/*!
 * filter-type <https://github.com/jonschlinkert/filter-type>
 *
 * Copyright (c) 2014 Jon Schlinkert, contributors.
 * Licensed under the MIT License
 */

'use strict';

var should = require('should');
var filter = require('./');

var arr = [1, 'a', {aaa: 'bbb'}, 3, 'b', 'c', 7, ['foo', 'bar'], {ccc: 'ddd'}, function one(){}];

describe('filter', function () {
  it('should filter objects:', function () {
    filter(arr, 'object').should.eql([{aaa: 'bbb'}, {ccc: 'ddd'}]);
  });

  it('should filter arrays:', function () {
    filter(arr, 'array').should.eql([['foo', 'bar']]);
  });

  it('should filter numbers:', function () {
    filter(arr, 'number').should.eql([1, 3, 7]);
  });

  it('should filter strings:', function () {
    filter(arr, 'string').should.eql(['a', 'b', 'c']);
  });

  it('should filter functions:', function () {
    filter(arr, 'function').should.eql([function one(){}]);
  });
});
<commit_msg>adds regex test<commit_after>/*!
 * filter-type <https://github.com/jonschlinkert/filter-type>
 *
 * Copyright (c) 2014-2015 Jon Schlinkert.
 * Licensed under the MIT License
 */

'use strict';

var should = require('should');
var filter = require('./');

var arr = [/foo/, /bar/, 1, 'a', {aaa: 'bbb'}, 3, 'b', 'c', 7, ['foo', 'bar'], {ccc: 'ddd'}, function one(){}];

describe('filter', function () {
  it('should filter objects:', function () {
    filter(arr, 'object').should.eql([{aaa: 'bbb'}, {ccc: 'ddd'}]);
  });

  it('should filter regex:', function () {
    filter(arr, 'regexp').should.eql([/foo/, /bar/]);
  });

  it('should filter arrays:', function () {
    filter(arr, 'array').should.eql([['foo', 'bar']]);
  });

  it('should filter numbers:', function () {
    filter(arr, 'number').should.eql([1, 3, 7]);
  });

  it('should filter strings:', function () {
    filter(arr, 'string').should.eql(['a', 'b', 'c']);
  });

  it('should filter functions:', function () {
    filter(arr, 'function').should.eql([function one(){}]);
  });
});
<|endoftext|>


<commit_before>/*
 * @file                Background script.
 * @author              Mitchell Field <mitchell.field@live.com.au>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

// URLs of pages with state that isn't important enough to keep.
// Considered blank.
const BLANK_TAB_URLS = new Set([
  "about:blank",
  "about:newtab",
  "about:privatebrowsing",
  "chrome://newtab/"
]);

const SORT_MODES = new Map([
  ["none", 0],
  ["host_title_path", 1],
  ["host_path_title", 2],
  ["title_host_path", 3]
]);

const PREFS = Object.assign(PREFS_DEFAULT);

// Get "sync" storage contents.
browser.storage.sync.get().then(async storedObject => {

  // Merge preferences with preferences from storage.
  Object.assign(PREFS, PREFS_DEFAULT, storedObject.preferences);
  return updateUI();
});

// Set browser action.
browser.browserAction.onClicked.addListener(onBrowserAction);

// Listen to changes in storage.
browser.storage.onChanged.addListener(onStorageChanged);

// Listen for tab updates.
browser.tabs.onUpdated.addListener(onTabUpdated);

class TabProps {
  constructor(tab, windowProps, containers) {
    const {
      active,
      cookieStoreId = null,
      id,
      index,
      status,
      title,
      url,
      windowId
    } = tab;

    const {
      protocol = ":",
      hostname = "",
      pathname = '/',
      searchParams = "",
      hash = "#"
    } = new URL(url || "");

    Object.assign(this, {
      _lowerDomainTokens: null,
      _pathnameTokens: null,
      _tldTokens: null,
      containerIndex: containers && cookieStoreId !== null ?
        containers.get(cookieStoreId) ?? -1 : -1,
      hasHTTPScheme: protocol === "https:" || protocol === "http:",
      hasPathname: pathname !== "/",
      hash,
      hostname,
      id,
      index,
      isActive: active,
      isBlank: BLANK_TAB_URLS.has(url),
      isDuplicate: false,
      pathname,
      queryString: searchParams.toString(),
      scheme: protocol,
      status,
      tab,
      title: title || "",
      url,
      windowId,
      windowProps
    });
  }

  get lowerDomainTokens() {
    if (!this._lowerDomainTokens) {
      const hostnameTokens = this.windowProps.getHostnameTokens(this.hostname);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._lowerDomainTokens;
  }

  get pathnameTokens() {
    if (!this._pathnameTokens) {
      this._pathnameTokens = this.windowProps.getPathnameTokens(this.pathname);
    }
    return this._pathnameTokens;
  }

  get tldTokens() {
    if (!this._tldTokens) {
      const hostnameTokens = this.windowProps.getHostnameTokens(this.hostname);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._tldTokens;
  }
}

class WindowProps {
  constructor(windowId) {
    WindowProps.windows.add(windowId);
    this.windowId = windowId;
    this.hostnameTokenCache = new Map();
    this.pathnameTokenCache = new Map();
  }

  /*
   * Roughly split hostname into top-level and lower-level domain tokens.
   *
   * @param hostname      hostname
   * @return              [top-level domain tokens, lower-level domain tokens]
   */
  getHostnameTokens(hostname) {
    let hostnameTokens = this.hostnameTokenCache.get(hostname);
    if (!hostnameTokens) {
      const tokens = hostname.split('.').reverse();
      const splitIndex = (tokens.length > 2 && tokens[1].length <= 3) ? 2 : 1;
      hostnameTokens = [tokens.slice(0, splitIndex), tokens.slice(splitIndex)];
      this.hostnameTokenCache.set(hostname, hostnameTokens);
    }
    return hostnameTokens;
  }

  /*
   * Split pathname into tokens.
   *
   * @param hostname      pathname
   * @return              [pathname tokens]
   */
  getPathnameTokens(pathname) {
    let pathnameTokens = this.pathnameTokenCache.get(pathname);
    if (!pathnameTokens) {
      pathnameTokens = pathname.split("/");
      this.pathnameTokenCache.set(pathname, pathnameTokens);
    }
    return pathnameTokens;
  }

  clear() {
    this.hostnameTokenCache.clear();
    this.pathnameTokenCache.clear();
    WindowProps.windows.delete(this.windowId);
  }
}

WindowProps.windows = new Set();

WindowProps.hasWindowById = function(windowId) {
  "use strict";
  return WindowProps.windows.has(windowId);
}

/*
 * Called when the "browser action" is invoked.
 */
function onBrowserAction(tab, onClickData) {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_browser_action === "true" &&
    (PREFS.pref_tabs_sort_by_container === "true" ||
    PREFS.pref_tabs_sort_by_parts !== "none");
  const deduplicate = PREFS.pref_tabs_deduplicate_on_browser_action === "true";

  if (sort || deduplicate)
    return processTabs(tab.windowId, sort, deduplicate);

  // The browser action is not configured to sort nor deduplicate tabs.
  return browser.runtime.openOptionsPage();
}

/*
 * Called when a storage area is changed.
 */
async function onStorageChanged(changes, areaName) {
  "use strict";
  if (areaName !== "sync" || !("preferences" in changes)) {
    return;
  }

  Object.assign(PREFS, changes.preferences.newValue);
  return updateUI();
}

function onTabUpdated(tabId, changeInfo, tab) {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_update === "true" &&
    (PREFS.pref_tabs_sort_by_container === "true" ||
    PREFS.pref_tabs_sort_by_parts !== "none");
  const deduplicate = PREFS.pref_tabs_deduplicate_on_update === "true";

  if (changeInfo.status !== "complete" || !changeInfo.status) {
    return;
  }

  if (sort || deduplicate)
    return processTabs(tab.windowId, sort, deduplicate);
}

/*
 * Updates UI elements such as titles and descriptions.
 */
function updateUI() {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_browser_action === "true";
  const deduplicate = PREFS.pref_tabs_deduplicate_on_browser_action === "true";

  // Default browser action title.
  let titleID = "browser_action_none_label";
  let shortcutDescriptionID = "browser_action_shortcut_none_label";

  if (sort) {
    if (deduplicate) {

      // Sort and deduplicate.
      titleID = "browser_action_sort_deduplicate_label";
      shortcutDescriptionID = "browser_action_shortcut_sort_deduplicate_label";
    } else {

      // Sort.
      titleID = "browser_action_sort_label";
      shortcutDescriptionID = "browser_action_shortcut_sort_label";
    }
  } else if (deduplicate) {

    // Deduplicate.
    titleID = "browser_action_deduplicate_label";
    shortcutDescriptionID = "browser_action_deduplicate_label";
  }

  // Set browser action title.
  browser.browserAction.setTitle({
    title: browser.i18n.getMessage(titleID)
  });

  // Set browser action shortcut description.
  if ("update" in browser.commands) {
    browser.commands.update({
      name: "_execute_browser_action",
      description: browser.i18n.getMessage(shortcutDescriptionID)
    });
  }
}

/*
 * Sorts, deduplicates, and removes low-priority (or blank) tabs.
 *
 * @param windowId      window ID
 * @param sort          sort tabs
 * @param deduplicate   deduplicate tabs
 */
async function processTabs(windowId, sort, deduplicate) {
  "use strict";
  let index;
  let tabPropsArray = [];

  if (WindowProps.hasWindowById(windowId))
    return Promise.resolve();

  const windowProps = new WindowProps(windowId);

  return browser.tabs.query({
    pinned: false,
    windowId,
  }).then(async unpinnedTabs => {
    if (!unpinnedTabs || unpinnedTabs.length === 0) {
      return Promise.reject(
        new Error(browser.i18n.getMessage("error_tabs_none")));
    }

    // Get first tab index.
    ({ index } = unpinnedTabs[0]);

    let containers = null;

    if ("contextualIdentities" in browser) {
      try {
        const containersArray = await browser.contextualIdentities.query({});
        containers = new Map(
          containersArray.map((c, index) => [c.cookieStoreId, index]));
      } catch (err) {}
    };

    tabPropsArray = unpinnedTabs.map(
      unpinnedTab => new TabProps(unpinnedTab, windowProps, containers));

    if (!sort)
      return Promise.resolve();

    // Sort and move tabs into place.
    return browser.tabs.move(
      tabPropsArray.sort(compareTabsOrder).map(tabProps => tabProps.id),
      { index }
    );
  }, err => {
    return Promise.reject(err);
  }).then(() => {

    // Detect duplicate tabs.
    if (deduplicate) {
      let tabPropsA = null;
      tabPropsArray.sort(compareTabsSimilarity).forEach(tabPropsB => {
        if (tabPropsA && compareTabsSimilarity(tabPropsA, tabPropsB) === 0)
          tabPropsA.isDuplicate = true;
        tabPropsA = tabPropsB;
      });
    }

    // Filter duplicate and blank tabs.
    const unwantedTabs = tabPropsArray.filter(tabProps =>
      tabProps.status === "complete" &&
        (tabProps.isBlank || deduplicate && tabProps.isDuplicate)
    );

    if (index === 0 && tabPropsArray.length === unwantedTabs.length) {

      // Create a new tab to remain after culling.
      return browser.tabs.create({
        active: false,
        windowId: windowId
      }).finally(browser.tabs.remove(unwantedTabs.map(tab => tab.id)));
    } else {
      return browser.tabs.remove(unwantedTabs.map(tab => tab.id));
    }
  }, err => {
    return Promise.reject(err);
  }).catch(err => {

    // Error caught. Do nothing.
  }).finally(() => {
    tabPropsArray.length = 0;
    windowProps.clear();
    return Promise.resolve();
  });
}

/*
 * Compares tabs to determine order.
 *
 * @param propsA        first tab properties
 * @param propsB        second tab properties
 * @return              numeric result
 */
function compareTabsOrder(propsA, propsB) {
  "use strict";

  const containerDiff = propsA.containerIndex - propsB.containerIndex;
  if (PREFS.pref_tabs_sort_by_container === "true" && containerDiff !== 0)
    return containerDiff;

  // Map the string preference value to a number.
  const sortMode = SORT_MODES.get(PREFS.pref_tabs_sort_by_parts);
  if (sortMode === 0)
    return 0;

  const titleDiff = propsA.title.localeCompare(propsB.title);

  return (sortMode === 3 ? titleDiff : 0) || // title-host-path
    (propsA.hasHTTPScheme && propsB.hasHTTPScheme ?
      0 : propsA.scheme.localeCompare(propsB.scheme)) ||
    compareTokens(propsA.lowerDomainTokens, propsB.lowerDomainTokens) ||
    compareTokens(propsA.tldTokens, propsB.tldTokens) ||
    (propsA.hasPathname - propsB.hasPathname) ||
    (sortMode === 1 ? titleDiff : 0) || // host-title-path
    compareTokens(propsA.pathnameTokens, propsB.pathnameTokens) ||
    (PREFS.pref_tabs_sort_by_query_string === "true" ?
      propsA.queryString.localeCompare(propsB.queryString) : 0) ||
    propsA.hash.localeCompare(propsB.hash) ||
    (sortMode === 2 ? titleDiff : 0); // host-path-title
}

/*
 * Compares tabs to determine similarity.
 *
 * @param propsA        first tab properties
 * @param propsB        second tab properties
 * @return              numeric result
 */
function compareTabsSimilarity(propsA, propsB) {
  "use strict";

  return (propsA.containerIndex - propsB.containerIndex) ||
    (!propsA.hasHTTPScheme || !propsB.hasHTTPScheme ?
      propsA.scheme.localeCompare(propsB.scheme) : 0) ||
    compareTokens(propsA.lowerDomainTokens, propsB.lowerDomainTokens) ||
    compareTokens(propsA.tldTokens, propsB.tldTokens) ||
    (propsA.hasPathname - propsB.hasPathname) ||
    compareTokens(propsA.pathnameTokens, propsB.pathnameTokens) ||
    propsA.queryString.localeCompare(propsB.queryString) ||
    propsA.hash.localeCompare(propsB.hash) ||
    propsA.title.localeCompare(propsB.title);
}

/*
 * Compares a list of tokens.
 *
 * Compare a token from one array to its corresponding token in the other array
 * until they sort differently or an array runs out of tokens to compare.
 *
 * @param tokensA       first array of tokens for comparison
 * @param tokensB       second array of tokens for comparison
 * @return              comparison numeric result
 */
function compareTokens(tokensA, tokensB) {
  "use strict";
  const tokensLengthA = tokensA.length;
  const tokensLengthB = tokensB.length;
  const shortestLength = Math.min(tokensLengthA, tokensLengthB);
  let result = 0;

  for (let index = 0; index < shortestLength; ++index) {
    if ((result = tokensA[index].localeCompare(tokensB[index])) !== 0)
      break;
  }

  return result === 0 ? Math.sign(tokensLengthA - tokensLengthB) : result;
}
<commit_msg>Optimise token comparison.<commit_after>/*
 * @file                Background script.
 * @author              Mitchell Field <mitchell.field@live.com.au>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

// URLs of pages with state that isn't important enough to keep.
// Considered blank.
const BLANK_TAB_URLS = new Set([
  "about:blank",
  "about:newtab",
  "about:privatebrowsing",
  "chrome://newtab/"
]);

const SORT_MODES = new Map([
  ["none", 0],
  ["host_title_path", 1],
  ["host_path_title", 2],
  ["title_host_path", 3]
]);

const PREFS = Object.assign(PREFS_DEFAULT);

// Get "sync" storage contents.
browser.storage.sync.get().then(async storedObject => {

  // Merge preferences with preferences from storage.
  Object.assign(PREFS, PREFS_DEFAULT, storedObject.preferences);
  return updateUI();
});

// Set browser action.
browser.browserAction.onClicked.addListener(onBrowserAction);

// Listen to changes in storage.
browser.storage.onChanged.addListener(onStorageChanged);

// Listen for tab updates.
browser.tabs.onUpdated.addListener(onTabUpdated);

class TabProps {
  constructor(tab, windowProps, containers) {
    const {
      active,
      cookieStoreId = null,
      id,
      index,
      status,
      title,
      url,
      windowId
    } = tab;

    const {
      protocol = ":",
      hostname = "",
      pathname = '/',
      searchParams = "",
      hash = "#"
    } = new URL(url || "");

    Object.assign(this, {
      _lowerDomainTokens: null,
      _pathnameTokens: null,
      _tldTokens: null,
      containerIndex: containers && cookieStoreId !== null ?
        containers.get(cookieStoreId) ?? -1 : -1,
      hasHTTPScheme: protocol === "https:" || protocol === "http:",
      hasPathname: pathname !== "/",
      hash,
      hostname,
      id,
      index,
      isActive: active,
      isBlank: BLANK_TAB_URLS.has(url),
      isDuplicate: false,
      pathname,
      queryString: searchParams.toString(),
      scheme: protocol,
      status,
      tab,
      title: title || "",
      url,
      windowId,
      windowProps
    });
  }

  get lowerDomainTokens() {
    if (!this._lowerDomainTokens) {
      const hostnameTokens = this.windowProps.getHostnameTokens(this.hostname);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._lowerDomainTokens;
  }

  get pathnameTokens() {
    if (!this._pathnameTokens) {
      this._pathnameTokens = this.windowProps.getPathnameTokens(this.pathname);
    }
    return this._pathnameTokens;
  }

  get tldTokens() {
    if (!this._tldTokens) {
      const hostnameTokens = this.windowProps.getHostnameTokens(this.hostname);
      this._tldTokens = hostnameTokens[0];
      this._lowerDomainTokens = hostnameTokens[1];
    }
    return this._tldTokens;
  }
}

class WindowProps {
  constructor(windowId) {
    WindowProps.windows.add(windowId);
    this.windowId = windowId;
    this.hostnameTokenCache = new Map();
    this.pathnameTokenCache = new Map();
  }

  /*
   * Roughly split hostname into top-level and lower-level domain tokens.
   *
   * @param hostname      hostname
   * @return              [top-level domain tokens, lower-level domain tokens]
   */
  getHostnameTokens(hostname) {
    let hostnameTokens = this.hostnameTokenCache.get(hostname);
    if (!hostnameTokens) {
      const tokens = hostname.split('.').reverse();
      const splitIndex = (tokens.length > 2 && tokens[1].length <= 3) ? 2 : 1;
      hostnameTokens = [tokens.slice(0, splitIndex), tokens.slice(splitIndex)];
      this.hostnameTokenCache.set(hostname, hostnameTokens);
    }
    return hostnameTokens;
  }

  /*
   * Split pathname into tokens.
   *
   * @param hostname      pathname
   * @return              [pathname tokens]
   */
  getPathnameTokens(pathname) {
    let pathnameTokens = this.pathnameTokenCache.get(pathname);
    if (!pathnameTokens) {
      pathnameTokens = pathname.split("/");
      this.pathnameTokenCache.set(pathname, pathnameTokens);
    }
    return pathnameTokens;
  }

  clear() {
    this.hostnameTokenCache.clear();
    this.pathnameTokenCache.clear();
    WindowProps.windows.delete(this.windowId);
  }
}

WindowProps.windows = new Set();

WindowProps.hasWindowById = function(windowId) {
  "use strict";
  return WindowProps.windows.has(windowId);
}

/*
 * Called when the "browser action" is invoked.
 */
function onBrowserAction(tab, onClickData) {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_browser_action === "true" &&
    (PREFS.pref_tabs_sort_by_container === "true" ||
    PREFS.pref_tabs_sort_by_parts !== "none");
  const deduplicate = PREFS.pref_tabs_deduplicate_on_browser_action === "true";

  if (sort || deduplicate)
    return processTabs(tab.windowId, sort, deduplicate);

  // The browser action is not configured to sort nor deduplicate tabs.
  return browser.runtime.openOptionsPage();
}

/*
 * Called when a storage area is changed.
 */
async function onStorageChanged(changes, areaName) {
  "use strict";
  if (areaName !== "sync" || !("preferences" in changes)) {
    return;
  }

  Object.assign(PREFS, changes.preferences.newValue);
  return updateUI();
}

function onTabUpdated(tabId, changeInfo, tab) {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_update === "true" &&
    (PREFS.pref_tabs_sort_by_container === "true" ||
    PREFS.pref_tabs_sort_by_parts !== "none");
  const deduplicate = PREFS.pref_tabs_deduplicate_on_update === "true";

  if (changeInfo.status !== "complete" || !changeInfo.status) {
    return;
  }

  if (sort || deduplicate)
    return processTabs(tab.windowId, sort, deduplicate);
}

/*
 * Updates UI elements such as titles and descriptions.
 */
function updateUI() {
  "use strict";
  const sort = PREFS.pref_tabs_sort_on_browser_action === "true";
  const deduplicate = PREFS.pref_tabs_deduplicate_on_browser_action === "true";

  // Default browser action title.
  let titleID = "browser_action_none_label";
  let shortcutDescriptionID = "browser_action_shortcut_none_label";

  if (sort) {
    if (deduplicate) {

      // Sort and deduplicate.
      titleID = "browser_action_sort_deduplicate_label";
      shortcutDescriptionID = "browser_action_shortcut_sort_deduplicate_label";
    } else {

      // Sort.
      titleID = "browser_action_sort_label";
      shortcutDescriptionID = "browser_action_shortcut_sort_label";
    }
  } else if (deduplicate) {

    // Deduplicate.
    titleID = "browser_action_deduplicate_label";
    shortcutDescriptionID = "browser_action_deduplicate_label";
  }

  // Set browser action title.
  browser.browserAction.setTitle({
    title: browser.i18n.getMessage(titleID)
  });

  // Set browser action shortcut description.
  if ("update" in browser.commands) {
    browser.commands.update({
      name: "_execute_browser_action",
      description: browser.i18n.getMessage(shortcutDescriptionID)
    });
  }
}

/*
 * Sorts, deduplicates, and removes low-priority (or blank) tabs.
 *
 * @param windowId      window ID
 * @param sort          sort tabs
 * @param deduplicate   deduplicate tabs
 */
async function processTabs(windowId, sort, deduplicate) {
  "use strict";
  let index;
  let tabPropsArray = [];

  if (WindowProps.hasWindowById(windowId))
    return Promise.resolve();

  const windowProps = new WindowProps(windowId);

  return browser.tabs.query({
    pinned: false,
    windowId,
  }).then(async unpinnedTabs => {
    if (!unpinnedTabs || unpinnedTabs.length === 0) {
      return Promise.reject(
        new Error(browser.i18n.getMessage("error_tabs_none")));
    }

    // Get first tab index.
    ({ index } = unpinnedTabs[0]);

    let containers = null;

    if ("contextualIdentities" in browser) {
      try {
        const containersArray = await browser.contextualIdentities.query({});
        containers = new Map(
          containersArray.map((c, index) => [c.cookieStoreId, index]));
      } catch (err) {}
    };

    tabPropsArray = unpinnedTabs.map(
      unpinnedTab => new TabProps(unpinnedTab, windowProps, containers));

    if (!sort)
      return Promise.resolve();

    // Sort and move tabs into place.
    return browser.tabs.move(
      tabPropsArray.sort(compareTabsOrder).map(tabProps => tabProps.id),
      { index }
    );
  }, err => {
    return Promise.reject(err);
  }).then(() => {

    // Detect duplicate tabs.
    if (deduplicate) {
      let tabPropsA = null;
      tabPropsArray.sort(compareTabsSimilarity).forEach(tabPropsB => {
        if (tabPropsA && compareTabsSimilarity(tabPropsA, tabPropsB) === 0)
          tabPropsA.isDuplicate = true;
        tabPropsA = tabPropsB;
      });
    }

    // Filter duplicate and blank tabs.
    const unwantedTabs = tabPropsArray.filter(tabProps =>
      tabProps.status === "complete" &&
        (tabProps.isBlank || deduplicate && tabProps.isDuplicate)
    );

    if (index === 0 && tabPropsArray.length === unwantedTabs.length) {

      // Create a new tab to remain after culling.
      return browser.tabs.create({
        active: false,
        windowId: windowId
      }).finally(browser.tabs.remove(unwantedTabs.map(tab => tab.id)));
    } else {
      return browser.tabs.remove(unwantedTabs.map(tab => tab.id));
    }
  }, err => {
    return Promise.reject(err);
  }).catch(err => {

    // Error caught. Do nothing.
  }).finally(() => {
    tabPropsArray.length = 0;
    windowProps.clear();
    return Promise.resolve();
  });
}

/*
 * Compares tabs to determine order.
 *
 * @param propsA        first tab properties
 * @param propsB        second tab properties
 * @return              numeric result
 */
function compareTabsOrder(propsA, propsB) {
  "use strict";

  const containerDiff = propsA.containerIndex - propsB.containerIndex;
  if (PREFS.pref_tabs_sort_by_container === "true" && containerDiff !== 0)
    return containerDiff;

  // Map the string preference value to a number.
  const sortMode = SORT_MODES.get(PREFS.pref_tabs_sort_by_parts);
  if (sortMode === 0)
    return 0;

  const titleDiff = propsA.title.localeCompare(propsB.title);

  return (sortMode === 3 ? titleDiff : 0) || // title-host-path
    (propsA.hasHTTPScheme && propsB.hasHTTPScheme ?
      0 : propsA.scheme.localeCompare(propsB.scheme)) ||
    compareTokens(propsA.lowerDomainTokens, propsB.lowerDomainTokens) ||
    compareTokens(propsA.tldTokens, propsB.tldTokens) ||
    (propsA.hasPathname - propsB.hasPathname) ||
    (sortMode === 1 ? titleDiff : 0) || // host-title-path
    compareTokens(propsA.pathnameTokens, propsB.pathnameTokens) ||
    (PREFS.pref_tabs_sort_by_query_string === "true" ?
      propsA.queryString.localeCompare(propsB.queryString) : 0) ||
    propsA.hash.localeCompare(propsB.hash) ||
    (sortMode === 2 ? titleDiff : 0); // host-path-title
}

/*
 * Compares tabs to determine similarity.
 *
 * @param propsA        first tab properties
 * @param propsB        second tab properties
 * @return              numeric result
 */
function compareTabsSimilarity(propsA, propsB) {
  "use strict";

  return (propsA.containerIndex - propsB.containerIndex) ||
    (!propsA.hasHTTPScheme || !propsB.hasHTTPScheme ?
      propsA.scheme.localeCompare(propsB.scheme) : 0) ||
    compareTokens(propsA.lowerDomainTokens, propsB.lowerDomainTokens) ||
    compareTokens(propsA.tldTokens, propsB.tldTokens) ||
    (propsA.hasPathname - propsB.hasPathname) ||
    compareTokens(propsA.pathnameTokens, propsB.pathnameTokens) ||
    propsA.queryString.localeCompare(propsB.queryString) ||
    propsA.hash.localeCompare(propsB.hash) ||
    propsA.title.localeCompare(propsB.title);
}

/*
 * Compares a list of tokens.
 *
 * Compare a token from one array to its corresponding token in the other array
 * until they sort differently or an array runs out of tokens to compare.
 *
 * @param tokensA       first array of tokens for comparison
 * @param tokensB       second array of tokens for comparison
 * @return              comparison numeric result
 */
function compareTokens(tokensA, tokensB) {
  "use strict";
  const tokensLengthA = tokensA.length;
  const tokensLengthB = tokensB.length;
  const shortestLength = Math.min(tokensLengthA, tokensLengthB);

  for (let index = 0; index < shortestLength; ++index) {
    let result = tokensA[index].localeCompare(tokensB[index]);
    if (result)
      return result;
  }

  return tokensLengthA - tokensLengthB;
}
<|endoftext|>


<commit_before>/*
 * localeinfo.js - Encode locale-specific defaults
 *
 * Copyright © 2012-2013, JEDLSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// !depends ilibglobal.js locale.js

// !data localeinfo likelylocales

/**
 * @class
 * Create a new locale info instance. Locale info instances give information about
 * the default settings for a particular locale. These settings may be overridden
 * by various parts of the code, and should be used as a fall-back setting of last
 * resort.

 *
 * The optional options object holds extra parameters if they are necessary. The
 * current list of supported options are:
 *
 *

 *
onLoad - a callback function to call when the locale info object is fully
 * loaded. When the onLoad option is given, the localeinfo object will attempt to
 * load any missing locale data using the ilib loader callback.
 * When the constructor is done (even if the data is already preassembled), the
 * onLoad function is called with the current instance as a parameter, so this
 * callback can be used with preassembled or dynamic loading or a mix of the two.
 *
 *
sync - tell whether to load any missing locale data synchronously or
 * asynchronously. If this option is given as "false", then the "onLoad"
 * callback must be given, as the instance returned from this constructor will
 * not be usable for a while.
 *
 *
loadParams - an object containing parameters to pass to the
 * loader callback function when locale data is missing. The parameters are not
 * interpretted or modified in any way. They are simply passed along. The object
 * may contain any property/value pairs as long as the calling code is in
 * agreement with the loader callback function as to what those parameters mean.
 *

 *
 * If this copy of ilib is pre-assembled and all the data is already available,
 * or if the data was already previously loaded, then this constructor will call
 * the onLoad callback immediately when the initialization is done.
 * If the onLoad option is not given, this class will only attempt to load any
 * missing locale data synchronously.
 *
 * Depends directive: !depends localeinfo.js
 *
 * @constructor
 * @see {ilib.setLoaderCallback} for information about registering a loader callback
 * function
 * @param {ilib.Locale|string=} locale the locale for which the info is sought, or undefined for
 * @param {Object=} options the locale for which the info is sought, or undefined for
 * the current locale
 */
ilib.LocaleInfo = function(locale, options) {
	var sync = true;

	/* these are all the defaults. Essentially, en-US */
	this.info = ilib.data.localeinfo;
	this.loadParams = {};

	switch (typeof(locale)) {
		case "string":
			this.locale = new ilib.Locale(locale);
			break;
		default:
		case "undefined":
			this.locale = new ilib.Locale();
			break;
		case "object":
			this.locale = locale;
			break;
	}

	if (options) {
		if (typeof(options.sync) !== 'undefined') {
			sync = (options.sync == true);
		}

		if (typeof(options.loadParams) !== 'undefined') {
			this.loadParams = options.loadParams;
		}
	}

	if (!ilib.LocaleInfo.cache) {
		ilib.LocaleInfo.cache = {};
	}

	ilib.loadData(ilib.LocaleInfo, this.locale, "localeinfo", sync, this.loadParams, ilib.bind(this, function (info) {
		if (!info) {
			info = ilib.data.localeinfo;
			var spec = this.locale.getSpec().replace(/-/g, "_");
			ilib.LocaleInfo.cache[spec] = info;
		}
		this.info = info;
		if (options && typeof(options.onLoad) === 'function') {
			options.onLoad(this);
		}
	}));
};

ilib.LocaleInfo.prototype = {
	/**
	 * Return whether this locale commonly uses the 12- or the 24-hour clock.
	 *
	 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24"
	 * if the locale commonly uses a 24-hour clock.
	 */
	getClock: function() {
		return this.info.clock;
	},

	/**
	 * Return the locale that this info object was created with.
	 * @returns {ilib.Locale} The locale spec of the locale used to construct this info instance
	 */
	getLocale: function () {
		return this.locale;
	},

	/**
	 * Return the name of the measuring system that is commonly used in the given locale.
	 * Valid values are "uscustomary", "imperial", and "metric".
	 *
	 * @returns {string} The name of the measuring system commonly used in the locale
	 */
	getUnits: function () {
		return this.info.units;
	},

	/**
	 * Return the name of the calendar that is commonly used in the given locale.
	 *
	 * @returns {string} The name of the calendar commonly used in the locale
	 */
	getCalendar: function () {
		return this.info.calendar;
	},

	/**
	 * Return the day of week that starts weeks in the current locale. Days are still
	 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars
	 * should be displayed and weeks calculated with the day of week returned from this
	 * function as the first day of the week.
	 *
	 * @returns {number} the day of the week that starts weeks in the current locale.
	 */
	getFirstDayOfWeek: function () {
		return this.info.firstDayOfWeek;
	},

	/**
	 * Return the default time zone for this locale. Many locales span across multiple
	 * time zones. In this case, the time zone with the largest population is chosen
	 * to represent the locale. This is obviously not that accurate, but then again,
	 * this method's return value should only be used as a default anyways.
	 * @returns {string} the default time zone for this locale.
	 */
	getTimeZone: function () {
		return this.info.timezone;
	},

	/**
	 * Return the decimal separator for formatted numbers in this locale.
	 * @returns {string} the decimal separator char
	 */
	getDecimalSeparator: function () {
		return this.info.numfmt.decimalChar;
	},

	/**
	 * Return the separator character used to separate groups of digits on the
	 * integer side of the decimal character.
	 * @returns {string} the grouping separator char
	 */
	getGroupingSeparator: function () {
		return this.info.numfmt.groupChar;
	},

	/**
	 * Return the minimum number of digits grouped together on the integer side
	 * for the first (primary) group.
	 * In western European cultures, groupings are in 1000s, so the number of digits
	 * is 3.
	 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping
	 */
	getPrimaryGroupingDigits: function () {
		return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' ?  this.info.numfmt.prigroupSize : this.info.numfmt.groupSize) || 0;
	},

	/**
	 * Return the minimum number of digits grouped together on the integer side
	 * for the second or more (secondary) group.

	 *
	 * In western European cultures, all groupings are by 1000s, so the secondary
	 * size should be 0 because there is no secondary size. In general, if this
	 * method returns 0, then all groupings are of the primary size.

	 *
	 * For some other cultures, the first grouping (primary)
	 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be
	 * written as: "1,00,000".
	 *
	 * @returns {number} the number of digits in a secondary grouping, or 0 for no
	 * secondary grouping.
	 */
	getSecondaryGroupingDigits: function () {
		return this.info.numfmt.secgroupSize || 0;
	},

	/**
	 * Return the format template used to format percentages in this locale.
	 * @returns {string} the format template for formatting percentages
	 */
	getPercentageFormat: function () {
		return this.info.numfmt.pctFmt;
	},

	getCurrencyFormat: function () {
		return this.info.numfmt.curFmt;
	},
	/**
	 * Return the symbol used for percentages in this locale.
	 * @returns {string} the symbol used for percentages in this locale
	 */
	getPercentageSymbol: function () {
		return this.info.numfmt.pctChar || "%";
	},

	getNegativeNumberFormat: function () {
			return this.info.numfmt.negativenumFmt;
	},

	getNegativeCurrencyFormat: function () {
			return this.info.numfmt.negativecurFmt;
	},

	/**
	 * Return an object containing the format templates for formatting currencies
	 * in this locale. The object has a number of properties in it that each are
	 * a particular style of format. Normally, this contains a "common" and an "iso"
	 * style, but may contain others in the future.
	 * @returns {Object} an object containing the format templates for currencies
	 */
	getCurrencyFormats: function () {
		return this.info.currencyFormats;
	},

	/**
	 * Return the currency that is legal in the locale, or which is most commonly
	 * used in regular commerce.
	 * @returns {string} the ISO 4217 code for the currency of this locale
	 */
	getCurrency: function () {
		return this.info.currency;
	},

	/**
	 * If this locale typically uses a different type of rounding for numeric
	 * formatting other than halfdown, especially for currency, then it can be
	 * specified in the localeinfo. If the locale uses the default, then this
	 * method returns undefined. The locale's rounding method overrides the
	 * rounding method for the currency itself, which can sometimes shared
	 * between various locales so it is less specific.
	 * @returns {string} the name of the rounding mode typically used in this
	 * locale, or "halfdown" if the locale does not override the default
	 */
	getRoundingMode: function () {
		return this.info.numfmt.roundingMode;
	},

	/**
	 * Return the default script used to write text in the language of this
	 * locale. Text for most languages is written in only one script, but there
	 * are some languages where the text can be written in a number of scripts,
	 * depending on a variety of things such as the region, ethnicity, religion,
	 * etc. of the author. This method returns the default script for the
	 * locale, in which the language is most commonly written.

	 *
	 * The script is returned as an ISO 15924 4-letter code.
	 *
	 * @returns {string} the ISO 15924 code for the default script used to write
	 * text in this locale
	 */
	getDefaultScript: function() {
		return (this.info.scripts) ? this.info.scripts[0] : "Latn";
	},

	/**
	 * Return the script used for the current locale. If the current locale
	 * explicitly defines a script, then this script is returned. If not, then
	 * the default script for the locale is returned.
	 *
	 * @see ilib.LocaleInfo.getDefaultScript
	 * @returns {string} the ISO 15924 code for the script used to write
	 * text in this locale
	 */
	getScript: function() {
		return this.locale.getScript() || this.getDefaultScript();
	},

	/**
	 * Return an array of script codes which are used to write text in the current
	 * language. Text for most languages is written in only one script, but there
	 * are some languages where the text can be written in a number of scripts,
	 * depending on a variety of things such as the region, ethnicity, religion,
	 * etc. of the author. This method returns an array of script codes in which
	 * the language is commonly written.
	 *
	 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used
	 * to write text in this language
	 */
	getAllScripts: function() {
		return this.info.scripts || ["Latn"];
	},

	/**
	 * Return an ilib.Locale instance that is fully specified based on partial information
	 * given to the constructor of this locale info instance. For example, if the locale
	 * spec given to this locale info instance is simply "ru" (for the Russian language),
	 * then it will fill in the missing region and script tags and return a locale with
	 * the specifier "ru-RU-Cyrl". (ie. Russian language, Russian Federation, Cyrillic).
	 * Any one or two of the language, script, or region parts may be left unspecified,
	 * and the other one or two parts will be filled in automatically. If this
	 * class has no information about the given locale, then the locale of this
	 * locale info instance is returned unchanged.
	 *
	 * @returns {ilib.Locale} the most likely completion of the partial locale given
	 * to the constructor of this locale info instance
	 */
	getLikelyLocale: function () {
		if (typeof(ilib.data.likelylocales[this.locale.getSpec()]) === 'undefined') {
			return this.locale;
		}

		return new ilib.Locale(ilib.data.likelylocales[this.locale.getSpec()]);
	}
};
<commit_msg>added the functions to return native digits for locales and the default numbering system digits<commit_after>/*
 * localeinfo.js - Encode locale-specific defaults
 *
 * Copyright © 2012-2013, JEDLSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// !depends ilibglobal.js locale.js

// !data localeinfo likelylocales

/**
 * @class
 * Create a new locale info instance. Locale info instances give information about
 * the default settings for a particular locale. These settings may be overridden
 * by various parts of the code, and should be used as a fall-back setting of last
 * resort.

 *
 * The optional options object holds extra parameters if they are necessary. The
 * current list of supported options are:
 *
 *

 *
onLoad - a callback function to call when the locale info object is fully
 * loaded. When the onLoad option is given, the localeinfo object will attempt to
 * load any missing locale data using the ilib loader callback.
 * When the constructor is done (even if the data is already preassembled), the
 * onLoad function is called with the current instance as a parameter, so this
 * callback can be used with preassembled or dynamic loading or a mix of the two.
 *
 *
sync - tell whether to load any missing locale data synchronously or
 * asynchronously. If this option is given as "false", then the "onLoad"
 * callback must be given, as the instance returned from this constructor will
 * not be usable for a while.
 *
 *
loadParams - an object containing parameters to pass to the
 * loader callback function when locale data is missing. The parameters are not
 * interpretted or modified in any way. They are simply passed along. The object
 * may contain any property/value pairs as long as the calling code is in
 * agreement with the loader callback function as to what those parameters mean.
 *

 *
 * If this copy of ilib is pre-assembled and all the data is already available,
 * or if the data was already previously loaded, then this constructor will call
 * the onLoad callback immediately when the initialization is done.
 * If the onLoad option is not given, this class will only attempt to load any
 * missing locale data synchronously.
 *
 * Depends directive: !depends localeinfo.js
 *
 * @constructor
 * @see {ilib.setLoaderCallback} for information about registering a loader callback
 * function
 * @param {ilib.Locale|string=} locale the locale for which the info is sought, or undefined for
 * @param {Object=} options the locale for which the info is sought, or undefined for
 * the current locale
 */
ilib.LocaleInfo = function(locale, options) {
	var sync = true;

	/* these are all the defaults. Essentially, en-US */
	this.info = ilib.data.localeinfo;
	this.loadParams = {};

	switch (typeof(locale)) {
		case "string":
			this.locale = new ilib.Locale(locale);
			break;
		default:
		case "undefined":
			this.locale = new ilib.Locale();
			break;
		case "object":
			this.locale = locale;
			break;
	}

	if (options) {
		if (typeof(options.sync) !== 'undefined') {
			sync = (options.sync == true);
		}

		if (typeof(options.loadParams) !== 'undefined') {
			this.loadParams = options.loadParams;
		}
	}

	if (!ilib.LocaleInfo.cache) {
		ilib.LocaleInfo.cache = {};
	}

	ilib.loadData(ilib.LocaleInfo, this.locale, "localeinfo", sync, this.loadParams, ilib.bind(this, function (info) {
		if (!info) {
			info = ilib.data.localeinfo;
			var spec = this.locale.getSpec().replace(/-/g, "_");
			ilib.LocaleInfo.cache[spec] = info;
		}
		this.info = info;
		if (options && typeof(options.onLoad) === 'function') {
			options.onLoad(this);
		}
	}));
};

ilib.LocaleInfo.prototype = {
	/**
	 * Return whether this locale commonly uses the 12- or the 24-hour clock.
	 *
	 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24"
	 * if the locale commonly uses a 24-hour clock.
	 */
	getClock: function() {
		return this.info.clock;
	},

	/**
	 * Return the locale that this info object was created with.
	 * @returns {ilib.Locale} The locale spec of the locale used to construct this info instance
	 */
	getLocale: function () {
		return this.locale;
	},

	/**
	 * Return the name of the measuring system that is commonly used in the given locale.
	 * Valid values are "uscustomary", "imperial", and "metric".
	 *
	 * @returns {string} The name of the measuring system commonly used in the locale
	 */
	getUnits: function () {
		return this.info.units;
	},

	/**
	 * Return the name of the calendar that is commonly used in the given locale.
	 *
	 * @returns {string} The name of the calendar commonly used in the locale
	 */
	getCalendar: function () {
		return this.info.calendar;
	},

	/**
	 * Return the day of week that starts weeks in the current locale. Days are still
	 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars
	 * should be displayed and weeks calculated with the day of week returned from this
	 * function as the first day of the week.
	 *
	 * @returns {number} the day of the week that starts weeks in the current locale.
	 */
	getFirstDayOfWeek: function () {
		return this.info.firstDayOfWeek;
	},

	/**
	 * Return the default time zone for this locale. Many locales span across multiple
	 * time zones. In this case, the time zone with the largest population is chosen
	 * to represent the locale. This is obviously not that accurate, but then again,
	 * this method's return value should only be used as a default anyways.
	 * @returns {string} the default time zone for this locale.
	 */
	getTimeZone: function () {
		return this.info.timezone;
	},

	/**
	 * Return the decimal separator for formatted numbers in this locale.
	 * @returns {string} the decimal separator char
	 */
	getDecimalSeparator: function () {
		return this.info.numfmt.decimalChar;
	},

	/**
	 * Return the decimal separator for formatted numbers in this locale for native script.
	 * @returns {string} the decimal separator char
	 */
	getNativeDecimalSeparator: function () {
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.decimalChar||this.info.numfmt.decimalChar :  this.info.numfmt.decimalChar);
	},

	/**
	 * Return the separator character used to separate groups of digits on the
	 * integer side of the decimal character.
	 * @returns {string} the grouping separator char
	 */
	getGroupingSeparator: function () {
		return this.info.numfmt.groupChar;
	},

	/**
	 * Return the separator character used to separate groups of digits on the
	 * integer side of the decimal character for the native script if present other than the default script.
	 * @returns {string} the grouping separator char
	 */
	getNativeGroupingSeparator: function () {
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.groupChar||this.info.numfmt.groupChar :  this.info.numfmt.groupChar);
	},

	/**
	 * Return the minimum number of digits grouped together on the integer side
	 * for the first (primary) group.
	 * In western European cultures, groupings are in 1000s, so the number of digits
	 * is 3.
	 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping
	 */
	getPrimaryGroupingDigits: function () {
		return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' ?  this.info.numfmt.prigroupSize : this.info.numfmt.groupSize) || 0;
	},

	/**
	 * Return the minimum number of digits grouped together on the integer side
	 * for the second or more (secondary) group.

	 *
	 * In western European cultures, all groupings are by 1000s, so the secondary
	 * size should be 0 because there is no secondary size. In general, if this
	 * method returns 0, then all groupings are of the primary size.

	 *
	 * For some other cultures, the first grouping (primary)
	 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be
	 * written as: "1,00,000".
	 *
	 * @returns {number} the number of digits in a secondary grouping, or 0 for no
	 * secondary grouping.
	 */
	getSecondaryGroupingDigits: function () {
		return this.info.numfmt.secgroupSize || 0;
	},

	/**
	 * Return the format template used to format percentages in this locale.
	 * @returns {string} the format template for formatting percentages
	 */
	getPercentageFormat: function () {
		return this.info.numfmt.pctFmt;
	},
	/**
	 * Return the format template used to format currencies in this locale.
	 * @returns {string} the format template for formatting currencies
	 */
	getCurrencyFormat: function () {
		return this.info.numfmt.curFmt;
	},

	/**
	 * Return the format template used to format currencies in this locale for native script.
	 * @returns {string} the format template for formatting currencies for native script
	 */
	getNativeCurrencyFormat: function () {
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.curFmt||this.info.numfmt.curFmt :  this.info.numfmt.curFmt);
	},

	/**
	 * Return the symbol used for percentages in this locale.
	 * @returns {string} the symbol used for percentages in this locale
	 */
	getPercentageSymbol: function () {
		return this.info.numfmt.pctChar || "%";
	},
	/**
	 * Return the symbol used for exponential in this locale.
	 * @returns {string} the symbol used for exponential in this locale
	 */
	getExponential: function () {
		return this.info.numfmt.exponential ;
	},
	/**
	 * Return the symbol used for exponential in this locale for native script.
	 * @returns {string} the symbol used for exponential in this locale for native script
	 */
	getNativeExponential: function () {
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.exponential||this.info.numfmt.exponential :  this.info.numfmt.exponential );
	},
	/**
	 * Return the symbol used for percentages in this locale for native script.
	 * @returns {string} the symbol used for percentages in this locale for native script
	 */
	getNativePercentageSymbol: function () {
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.pctChar||this.info.numfmt.pctChar :  this.info.numfmt.pctChar || "%");

	},
	/**
	 * Return the format template used to format negative numbers in this locale.
	 * @returns {string} the format template for formatting negative numbers
	 */
	getNegativeNumberFormat: function () {
			return this.info.numfmt.negativenumFmt;
	},

	/**
	 * Return the format template used to format negative currencies in this locale.
	 * @returns {string} the format template for formatting negative currencies
	 */
	getNegativeCurrencyFormat: function () {
			return this.info.numfmt.negativecurFmt;
	},

	/**
	 * Return an object containing the format templates for formatting currencies
	 * in this locale. The object has a number of properties in it that each are
	 * a particular style of format. Normally, this contains a "common" and an "iso"
	 * style, but may contain others in the future.
	 * @returns {Object} an object containing the format templates for currencies
	 */
	getCurrencyFormats: function () {
		return this.info.currencyFormats;
	},

	/**
	 * Return the currency that is legal in the locale, or which is most commonly
	 * used in regular commerce.
	 * @returns {string} the ISO 4217 code for the currency of this locale
	 */
	getCurrency: function () {
		return this.info.currency;
	},

	/**
	 * Return the digits of the default script
	 * @returns {string} the digits used in the default script
	 */
	getDigits: function () {
		var num = "0123456789";
		return this.info.numfmt.digits|| num ;
	},

	/**
	 * Return the digits of the native script other than 0,1,2,3,4.....9
	 * @returns {string} the digits used in the default script
	 */
	getNativeDigits: function () {
		var num = "0123456789";
		return (typeof(this.info.native_numfmt) != 'undefined' ? this.info.native_numfmt.digits||this.info.numfmt.pctChar :  this.info.numfmt.digits || num);
	},

	/**
	 * If this locale typically uses a different type of rounding for numeric
	 * formatting other than halfdown, especially for currency, then it can be
	 * specified in the localeinfo. If the locale uses the default, then this
	 * method returns undefined. The locale's rounding method overrides the
	 * rounding method for the currency itself, which can sometimes shared
	 * between various locales so it is less specific.
	 * @returns {string} the name of the rounding mode typically used in this
	 * locale, or "halfdown" if the locale does not override the default
	 */
	getRoundingMode: function () {
		return this.info.numfmt.roundingMode;
	},

	/**
	 * Return the default script used to write text in the language of this
	 * locale. Text for most languages is written in only one script, but there
	 * are some languages where the text can be written in a number of scripts,
	 * depending on a variety of things such as the region, ethnicity, religion,
	 * etc. of the author. This method returns the default script for the
	 * locale, in which the language is most commonly written.

	 *
	 * The script is returned as an ISO 15924 4-letter code.
	 *
	 * @returns {string} the ISO 15924 code for the default script used to write
	 * text in this locale
	 */
	getDefaultScript: function() {
		return (this.info.scripts) ? this.info.scripts[0] : "Latn";
	},

	/**
	 * Return the script used for the current locale. If the current locale
	 * explicitly defines a script, then this script is returned. If not, then
	 * the default script for the locale is returned.
	 *
	 * @see ilib.LocaleInfo.getDefaultScript
	 * @returns {string} the ISO 15924 code for the script used to write
	 * text in this locale
	 */
	getScript: function() {
		return this.locale.getScript() || this.getDefaultScript();
	},

	/**
	 * Return an array of script codes which are used to write text in the current
	 * language. Text for most languages is written in only one script, but there
	 * are some languages where the text can be written in a number of scripts,
	 * depending on a variety of things such as the region, ethnicity, religion,
	 * etc. of the author. This method returns an array of script codes in which
	 * the language is commonly written.
	 *
	 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used
	 * to write text in this language
	 */
	getAllScripts: function() {
		return this.info.scripts || ["Latn"];
	},

	/**
	 * Return an ilib.Locale instance that is fully specified based on partial information
	 * given to the constructor of this locale info instance. For example, if the locale
	 * spec given to this locale info instance is simply "ru" (for the Russian language),
	 * then it will fill in the missing region and script tags and return a locale with
	 * the specifier "ru-RU-Cyrl". (ie. Russian language, Russian Federation, Cyrillic).
	 * Any one or two of the language, script, or region parts may be left unspecified,
	 * and the other one or two parts will be filled in automatically. If this
	 * class has no information about the given locale, then the locale of this
	 * locale info instance is returned unchanged.
	 *
	 * @returns {ilib.Locale} the most likely completion of the partial locale given
	 * to the constructor of this locale info instance
	 */
	getLikelyLocale: function () {
		if (typeof(ilib.data.likelylocales[this.locale.getSpec()]) === 'undefined') {
			return this.locale;
		}

		return new ilib.Locale(ilib.data.likelylocales[this.locale.getSpec()]);
	}
};
<|endoftext|>