<commit_before>(function( $, undefined ) {
    $.KBWidget({
        name: "KBaseGenomeOverview",
        parent: "kbaseWidget",
        version: "1.0.0",

        options: {
            genomeID: null,
            workspaceID: null,
            loadingImage: "../../widgets/images/ajax-loader.gif",
            kbCache: null,
            isInCard: false,
        },

        token: null,
        cdmiURL: "https://kbase.us/services/cdmi_api",
        $infoTable: null,
        noContigs: "No Contigs",

        init: function(options) {
            this._super(options);
            if (this.options.genomeID === null) {
                //throw an error
                return;
            }

            this.$messagePane = $("
")
                                .addClass("kbwidget-message-pane")
                                .hide();
            this.$elem.append(this.$messagePane);

            this.render();
            if (this.options.workspaceID === null)
                this.renderCentralStore();
            else
                this.renderWorkspace();

            return this;
        },

        render: function() {
            var self = this;

            this.$infoPanel = $("
");
            this.$infoPanel.append($("<button>")
                           .addClass("btn btn-primary")
                           .append("Show Description")
                           .attr("type", "button")
                           .on("click",
                               function(event) {
                                   self.trigger("showGenomeDescription",
                                       {
                                           genomeID: self.options.genomeID,
                                           workspaceID: self.options.workspaceID,
                                           kbCache: self.options.kbCache,
                                           event: event
                                       }
                                   );
                               })
                           );

			this.$infoPanel.append($("<button>")
                                 .addClass("btn btn-primary")
                                 .append("Literature Search")
								 .attr("type", "button")
								 .on("click",
									function(event) {

										if (typeof self.pubmedQuery === null) {
											self.renderError("Genome '" + self.options.genomeID + "' not found in the KBase Central Store.");
											return;
										}
										else {
											var query = self.pubmedQuery
											self.trigger("showLitWidget",
												{
												literature: query,
												workspaceID: self.options.workspaceID,
												genomeID: self.options.genomeID,
												kbCache: self.options.kbCache,
												event: event,
												}
											);
										}

									})
								);

            this.$infoTable = $("
")
                              .addClass("table table-striped table-bordered");
            this.$infoPanel.append($("
").append(this.$infoTable));

            this.$contigSelect = $("<select>")
                                 .addClass("form-control")
                                 .css({"width":"60%", "margin-right":"5px"})
                                 .append($("<option>")
                                         .attr("id", this.noContigs)
                                         .append(this.noContigs));

            var self = this;
            this.$contigButton = $("<button>")
                                 .addClass("btn btn-primary")
                                 .append("Show Contig")
                                 .click(function(event) {
                                    self.$elem.find("select option:selected").each(function() {
                                        var contigId = $(this).attr("id");
                                        if (contigId !== self.noContigs) {
                                            self.trigger("showContig",
                                                {
                                                    contig: $(this).attr("id"),
                                                    workspaceId: self.options.workspaceID,
                                                    genomeId: self.options.genomeID,
                                                    kbCache: self.options.kbCache,
                                                    event: event,
                                                }
                                            );
                                        }
                                    })
                                 });

            this.$infoPanel.append($("
")
                              .addClass("form-inline")
                              .append(this.$contigSelect)
                              .append(this.$contigButton));

            this.$infoPanel.hide();
            this.$elem.append(this.$infoPanel);

			// self.pubmedQuery = ""

        },

        addInfoRow: function(a, b) {
            return "
" + a + "	" + b + "
";
        },

        renderCentralStore: function() {
            this.cdmiClient = new CDMI_API(this.cdmiURL);
            this.entityClient = new CDMI_EntityAPI(this.cdmiURL);

            this.$infoPanel.hide();
            this.showMessage("");
            /**
             * Fields to show:
             * ID
             * Workspace (if from a workspace)
             * Owner (KBase Central Store vs. username)
             * Scientific Name
             * Domain
             * Complete
             * Size in bp
             * GC content (do we need this?)
             * # contigs
             * # features
             * # genes - PEGs
             * # RNA feats
             * Taxonomy
             */

            var self = this;
            this.entityClient.get_entity_Genome([this.options.genomeID],
                ['id', 'scientific_name', 'domain', 'complete', 'dna_size', 'source_id',
                 'contigs', 'gc_content', 'pegs', 'rnas'],

                $.proxy(function(genome) {
                    genome = genome[this.options.genomeID];
                    this.genome = genome; // store it for now.

                    if (!genome) {
                        this.renderError("Genome '" + this.options.genomeID + "' not found in the KBase Central Store.");
                        return;
                    }

					self.pubmedQuery = genome.scientific_name
					console.log(self.pubmedQuery)
                    this.$infoTable.empty()
                                   .append(this.addInfoRow("ID", genome.id))
                                   .append(this.addInfoRow("Name", genome.scientific_name))
                                   .append(this.addInfoRow("Domain", genome.domain))
//                                   .append(this.addInfoRow("Complete?", (genome.complete ? "Yes" : "No")))
                                   .append(this.addInfoRow("DNA Length", genome.dna_size))
                                   .append(this.addInfoRow("Source ID", genome.source_id))
                                   .append(this.addInfoRow("Number of Contigs", genome.contigs))
                                   .append(this.addInfoRow("GC Content", Number(genome.gc_content).toFixed(2) + " %"))
                                   .append(this.addInfoRow("Protein Encoding Genes", genome.pegs))
                                   .append(this.addInfoRow("RNAs", genome.rnas));

                    /*
                     * Here we go. Chain of callbacks.
                     * Get list of contigs, then get their lengths.
                     * Sort them by length and make a dropdown menu.
                     * Add a button that will open a new card with that contig's browser.
                     */
                    if (this.options.isInCard) {
                        this.cdmiClient.genomes_to_contigs([this.options.genomeID],
                            $.proxy(function(contigs) {
                                this.cdmiClient.contigs_to_lengths(contigs[this.options.genomeID],
                                    $.proxy(function(contigsToLengths) {
                                        this.populateContigSelector(contigsToLengths);
                                    }, this),
                                    this.renderError
                                );
                            }, this),

                            this.renderError
                        );
                    }

                    this.hideMessage();
                    this.$infoPanel.show();

                }, this),

                this.renderError
            );

        },

        populateContigSelector: function(contigsToLengths) {
            this.$contigSelect.empty();
            if (!contigsToLengths || contigsToLengths.length == 0)
                this.$contigSelect.append($("<option>")
                                          .attr("id", this.noContigs)
                                          .append(this.noContigs));
            for (var contig in contigsToLengths) {
                this.$contigSelect.append($("<option>")
                                          .attr("id", contig)
                                          .append(contig + " - " + contigsToLengths[contig] + " bp"));
            }
        },

        renderWorkspace: function() {
			self = this
            this.showMessage("");
            this.$infoPanel.hide();
            // console.log("rendering workspace genome");
            // console.log(this.options.kbCache);

            isInt = function(n) {
                return typeof n === 'number' && n % 1 == 0;
            };

            var obj = this.buildObjectIdentity(this.options.workspaceID, this.options.genomeID);

            var prom = this.options.kbCache.req('ws', 'get_objects', [obj]);
            $.when(prom).done($.proxy(function(genome) {
                // console.log(genome);
                genome = genome[0].data;
				self.pubmedQuery = genome.scientific_name
				console.log(self.pubmedQuery)

				var gcContent = "Unknown";
                var dnaLength = "Unknown";
                if (genome.dna_size && genome.dna_size != 0) {
                    dnaLength = genome.dna_size;
                    if (genome.gc_content) {
                        gcContent = Number(genome.gc_content);
                        if (isInt(gcContent)) {
                            if (dnaLength)
                                gcContent = (gcContent/dnaLength*100).toFixed(2) + " %";
                        }
                        else
                            gcContent = Number(gcContent.toFixed(2)) + " %";
                    }
                }

                this.$infoTable.empty()
                               .append(this.addInfoRow("ID", genome.id))
                               .append(this.addInfoRow("Name", genome.scientific_name))
                               .append(this.addInfoRow("Domain", genome.domain))
                               .append(this.addInfoRow("DNA Length", dnaLength))
                               .append(this.addInfoRow("Source ID", genome.source + ": " + genome.source_id))
                               .append(this.addInfoRow("Number of Contigs", genome.contig_ids ? genome.contig_ids.length : 0))
                               .append(this.addInfoRow("GC Content", gcContent))
                               .append(this.addInfoRow("Genetic Code", genome.genetic_code))
                               .append(this.addInfoRow("Number of features", genome.features.length));

                var contigsToLengths = {};
                if (genome.contig_ids && genome.contig_ids.length > 0) {
                    for (var i=0; i<genome.contig_ids.length; i++) {
                        var len = "Unknown";
                        if (genome.contig_lengths && genome.contig_lengths[i])
                            len = genome.contig_lengths[i];
                        contigsToLengths[genome.contig_ids[i]] = len;
                    }
                }
                /************
                 * TEMP CODE!
                 * INFER CONTIGS FROM FEATURE LIST!
                 * OMG THIS SUCKS THAT I HAVE TO DO THIS UNTIL FBA MODEL SERVICES IS FIXED!
                 * LOUD NOISES!
                 ************/
                else if (genome.features && genome.features.length > 0) {
                    var contigSet = {};
                    for (var i=0; i<genome.features.length; i++) {
                        var f = genome.features[i];
                        if (f.location && f.location[0][0])
                            contigsToLengths[f.location[0][0]] = "Unknown";
                    }

                }

                this.populateContigSelector(contigsToLengths);

                this.hideMessage();
                this.$infoPanel.show();

            }, this));
            $.when(prom).fail($.proxy(function(error) { this.renderError(error); }, this));
        },

        getData: function() {
            return {
                type: "Genome",
                id: this.options.genomeID,
                workspace: this.options.workspaceID,
                title: "Genome Overview"
            };
        },

        showMessage: function(message) {
            var span = $("").append(message);

            this.$messagePane.empty()
                             .append(span)
                             .show();
        },

        hideMessage: function() {
            this.$messagePane.hide();
        },

        buildObjectIdentity: function(workspaceID, objectID) {
            var obj = {};
            if (/^\d+$/.exec(workspaceID))
                obj['wsid'] = workspaceID;
            else
                obj['workspace'] = workspaceID;

            // same for the id
            if (/^\d+$/.exec(objectID))
                obj['objid'] = objectID;
            else
                obj['name'] = objectID;
            return obj;
        },

        renderError: function(error) {
            errString = "Sorry, an unknown error occurred";
            if (typeof error === "string")
                errString = error;
            else if (error.error && error.error.message)
                errString = error.error.message;

            var $errorDiv = $("
")
                            .addClass("alert alert-danger")
                            .append("Error:")
                            .append("
" + errString);
            this.$elem.empty();
            this.$elem.append($errorDiv);
        },

    });
})( jQuery );<commit_msg>added link from genome overview to obj graph<commit_after>(function( $, undefined ) {
    $.KBWidget({
        name: "KBaseGenomeOverview",
        parent: "kbaseWidget",
        version: "1.0.0",

        options: {
            genomeID: null,
            workspaceID: null,
            loadingImage: "../../widgets/images/ajax-loader.gif",
            kbCache: null,
            isInCard: false,
        },

        token: null,
        cdmiURL: "https://kbase.us/services/cdmi_api",
        $infoTable: null,
        noContigs: "No Contigs",

        init: function(options) {
            this._super(options);
            if (this.options.genomeID === null) {
                //throw an error
                return;
            }

            this.$messagePane = $("
")
                                .addClass("kbwidget-message-pane")
                                .hide();
            this.$elem.append(this.$messagePane);

            this.render();
            if (this.options.workspaceID === null)
                this.renderCentralStore();
            else
                this.renderWorkspace();

            return this;
        },

        render: function() {
            var self = this;

            this.$infoPanel = $("
");
            this.$infoPanel.append($("<button>")
                           .addClass("btn btn-primary")
                           .append("Show Description")
                           .attr("type", "button")
                           .on("click",
                               function(event) {
                                   self.trigger("showGenomeDescription",
                                       {
                                           genomeID: self.options.genomeID,
                                           workspaceID: self.options.workspaceID,
                                           kbCache: self.options.kbCache,
                                           event: event
                                       }
                                   );
                               })
                           );

			this.$infoPanel.append($("<button>")
                                 .addClass("btn btn-primary")
                                 .append("Related Publications")
								 .attr("type", "button")
								 .on("click",
									function(event) {

										if (typeof self.pubmedQuery === null) {
											self.renderError("Genome '" + self.options.genomeID + "' not found in the KBase Central Store.");
											return;
										}
										else {
											var query = self.pubmedQuery
											self.trigger("showLitWidget",
												{
												literature: query,
												workspaceID: self.options.workspaceID,
												genomeID: self.options.genomeID,
												kbCache: self.options.kbCache,
												event: event,
												}
											);
										}

									})
								);
                        if (self.options.workspaceID === null) {
                            // possibly we show other CDS related buttongs
                        } else {
                            // show ws related buttons
                            self.$infoPanel.append($("<button>")
                                 .addClass("btn btn-primary")
                                 .append("View Object Graph").attr("type", "button").on("click",
							function(event) {
                                                            window.location.href = "#/objgraphview/"+self.options.workspaceID+"/"+self.options.genomeID;
							}));
                        }

            this.$infoTable = $("
")
                              .addClass("table table-striped table-bordered");
            this.$infoPanel.append($("
").append(this.$infoTable));

            this.$contigSelect = $("<select>")
                                 .addClass("form-control")
                                 .css({"width":"60%", "margin-right":"5px"})
                                 .append($("<option>")
                                         .attr("id", this.noContigs)
                                         .append(this.noContigs));

            var self = this;
            this.$contigButton = $("<button>")
                                 .addClass("btn btn-primary")
                                 .append("Show Contig")
                                 .click(function(event) {
                                    self.$elem.find("select option:selected").each(function() {
                                        var contigId = $(this).attr("id");
                                        if (contigId !== self.noContigs) {
                                            self.trigger("showContig",
                                                {
                                                    contig: $(this).attr("id"),
                                                    workspaceId: self.options.workspaceID,
                                                    genomeId: self.options.genomeID,
                                                    kbCache: self.options.kbCache,
                                                    event: event,
                                                }
                                            );
                                        }
                                    })
                                 });

            this.$infoPanel.append($("
")
                              .addClass("form-inline")
                              .append(this.$contigSelect)
                              .append(this.$contigButton));

            this.$infoPanel.hide();
            this.$elem.append(this.$infoPanel);

			// self.pubmedQuery = ""

        },

        addInfoRow: function(a, b) {
            return "
" + a + "	" + b + "
";
        },

        renderCentralStore: function() {
            this.cdmiClient = new CDMI_API(this.cdmiURL);
            this.entityClient = new CDMI_EntityAPI(this.cdmiURL);

            this.$infoPanel.hide();
            this.showMessage("");
            /**
             * Fields to show:
             * ID
             * Workspace (if from a workspace)
             * Owner (KBase Central Store vs. username)
             * Scientific Name
             * Domain
             * Complete
             * Size in bp
             * GC content (do we need this?)
             * # contigs
             * # features
             * # genes - PEGs
             * # RNA feats
             * Taxonomy
             */

            var self = this;
            this.entityClient.get_entity_Genome([this.options.genomeID],
                ['id', 'scientific_name', 'domain', 'complete', 'dna_size', 'source_id',
                 'contigs', 'gc_content', 'pegs', 'rnas'],

                $.proxy(function(genome) {
                    genome = genome[this.options.genomeID];
                    this.genome = genome; // store it for now.

                    if (!genome) {
                        this.renderError("Genome '" + this.options.genomeID + "' not found in the KBase Central Store.");
                        return;
                    }

					self.pubmedQuery = genome.scientific_name
					console.log(self.pubmedQuery)
                    this.$infoTable.empty()
                                   .append(this.addInfoRow("ID", genome.id))
                                   .append(this.addInfoRow("Name", genome.scientific_name))
                                   .append(this.addInfoRow("Domain", genome.domain))
//                                   .append(this.addInfoRow("Complete?", (genome.complete ? "Yes" : "No")))
                                   .append(this.addInfoRow("DNA Length", genome.dna_size))
                                   .append(this.addInfoRow("Source ID", genome.source_id))
                                   .append(this.addInfoRow("Number of Contigs", genome.contigs))
                                   .append(this.addInfoRow("GC Content", Number(genome.gc_content).toFixed(2) + " %"))
                                   .append(this.addInfoRow("Protein Encoding Genes", genome.pegs))
                                   .append(this.addInfoRow("RNAs", genome.rnas));

                    /*
                     * Here we go. Chain of callbacks.
                     * Get list of contigs, then get their lengths.
                     * Sort them by length and make a dropdown menu.
                     * Add a button that will open a new card with that contig's browser.
                     */
                    if (this.options.isInCard) {
                        this.cdmiClient.genomes_to_contigs([this.options.genomeID],
                            $.proxy(function(contigs) {
                                this.cdmiClient.contigs_to_lengths(contigs[this.options.genomeID],
                                    $.proxy(function(contigsToLengths) {
                                        this.populateContigSelector(contigsToLengths);
                                    }, this),
                                    this.renderError
                                );
                            }, this),

                            this.renderError
                        );
                    }

                    this.hideMessage();
                    this.$infoPanel.show();

                }, this),

                this.renderError
            );

        },

        populateContigSelector: function(contigsToLengths) {
            this.$contigSelect.empty();
            if (!contigsToLengths || contigsToLengths.length == 0)
                this.$contigSelect.append($("<option>")
                                          .attr("id", this.noContigs)
                                          .append(this.noContigs));
            for (var contig in contigsToLengths) {
                this.$contigSelect.append($("<option>")
                                          .attr("id", contig)
                                          .append(contig + " - " + contigsToLengths[contig] + " bp"));
            }
        },

        renderWorkspace: function() {
			self = this
            this.showMessage("");
            this.$infoPanel.hide();
            // console.log("rendering workspace genome");
            // console.log(this.options.kbCache);

            isInt = function(n) {
                return typeof n === 'number' && n % 1 == 0;
            };

            var obj = this.buildObjectIdentity(this.options.workspaceID, this.options.genomeID);

            var prom = this.options.kbCache.req('ws', 'get_objects', [obj]);
            $.when(prom).done($.proxy(function(genome) {
                // console.log(genome);
                genome = genome[0].data;
				self.pubmedQuery = genome.scientific_name
				console.log(self.pubmedQuery)

				var gcContent = "Unknown";
                var dnaLength = "Unknown";
                if (genome.dna_size && genome.dna_size != 0) {
                    dnaLength = genome.dna_size;
                    if (genome.gc_content) {
                        gcContent = Number(genome.gc_content);
                        if (isInt(gcContent)) {
                            if (dnaLength)
                                gcContent = (gcContent/dnaLength*100).toFixed(2) + " %";
                        }
                        else
                            gcContent = Number(gcContent.toFixed(2)) + " %";
                    }
                }

                this.$infoTable.empty()
                               .append(this.addInfoRow("ID", genome.id))
                               .append(this.addInfoRow("Name", genome.scientific_name))
                               .append(this.addInfoRow("Domain", genome.domain))
                               .append(this.addInfoRow("DNA Length", dnaLength))
                               .append(this.addInfoRow("Source ID", genome.source + ": " + genome.source_id))
                               .append(this.addInfoRow("Number of Contigs", genome.contig_ids ? genome.contig_ids.length : 0))
                               .append(this.addInfoRow("GC Content", gcContent))
                               .append(this.addInfoRow("Genetic Code", genome.genetic_code))
                               .append(this.addInfoRow("Number of features", genome.features.length));

                var contigsToLengths = {};
                if (genome.contig_ids && genome.contig_ids.length > 0) {
                    for (var i=0; i<genome.contig_ids.length; i++) {
                        var len = "Unknown";
                        if (genome.contig_lengths && genome.contig_lengths[i])
                            len = genome.contig_lengths[i];
                        contigsToLengths[genome.contig_ids[i]] = len;
                    }
                }
                /************
                 * TEMP CODE!
                 * INFER CONTIGS FROM FEATURE LIST!
                 * OMG THIS SUCKS THAT I HAVE TO DO THIS UNTIL FBA MODEL SERVICES IS FIXED!
                 * LOUD NOISES!
                 ************/
                else if (genome.features && genome.features.length > 0) {
                    var contigSet = {};
                    for (var i=0; i<genome.features.length; i++) {
                        var f = genome.features[i];
                        if (f.location && f.location[0][0])
                            contigsToLengths[f.location[0][0]] = "Unknown";
                    }

                }

                this.populateContigSelector(contigsToLengths);

                this.hideMessage();
                this.$infoPanel.show();

            }, this));
            $.when(prom).fail($.proxy(function(error) { this.renderError(error); }, this));
        },

        getData: function() {
            return {
                type: "Genome",
                id: this.options.genomeID,
                workspace: this.options.workspaceID,
                title: "Genome Overview"
            };
        },

        showMessage: function(message) {
            var span = $("").append(message);

            this.$messagePane.empty()
                             .append(span)
                             .show();
        },

        hideMessage: function() {
            this.$messagePane.hide();
        },

        buildObjectIdentity: function(workspaceID, objectID) {
            var obj = {};
            if (/^\d+$/.exec(workspaceID))
                obj['wsid'] = workspaceID;
            else
                obj['workspace'] = workspaceID;

            // same for the id
            if (/^\d+$/.exec(objectID))
                obj['objid'] = objectID;
            else
                obj['name'] = objectID;
            return obj;
        },

        renderError: function(error) {
            errString = "Sorry, an unknown error occurred";
            if (typeof error === "string")
                errString = error;
            else if (error.error && error.error.message)
                errString = error.error.message;

            var $errorDiv = $("
")
                            .addClass("alert alert-danger")
                            .append("Error:")
                            .append("
" + errString);
            this.$elem.empty();
            this.$elem.append($errorDiv);
        },

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


<commit_before>$(document).ready(function() {
	// TODO if grouping stays disabled, this can be pulled back out of a function
	fancy_cells('#all-tasks.editable');

	setup_hours_events();
	setup_search();
	setup_filter_buttons();
	setup_group_arrows();
	setup_bugzilla($('tr.task'));

	$('#post-status').hide();
	$('.saving').css('visibility', 'hidden');
});

hours_cache = -1;
hours_timer = null;
function setup_hours_events() {
	$('td.hours img').css('opacity', 0);

	$('td.hours').hover(
		function() {
			// $('img', this).fadeIn();
			$('img', this).animate({opacity: 1});
		},
		function() {
			// $('img', this).fadeOut();
			$('img', this).animate({opacity: 0});
		}
	);

	$('td.hours img').hover(
		function() {
			$(this).attr('src', $(this).attr('src').match(/[^\.]+/) + '-lit.png');
		},
		function() {
			$(this).attr('src', $(this).attr('src').replace('-lit', ''));
		}
	);

	$('td.hours img').click(function() {
		task = $(this).parents('tr.task');
        field = $('input', $(this).parents('.hours'));
		old_val = val = parseInt(field.val(), 10);
		val += parseInt($(this).attr('amt'), 10);
		if(val < 0) {
			if(old_val == 0) {
				return;
			} else {
				val = 0;
			}
		}
		field.val('' + val)
		save_task(task, 'hours', val);
		if(!isPlanning) {
			set_status(task, val == 0 ? 'complete' : 'in progress');
		}
	});

	$("td.hours input").focus(function(event) {
		hours_cache = parseInt($('input', $(this).parents('.hours')).val(), 10);
	}).blur(function(event) {
		hours_blur($(this));
	}).keypress(function(event) {
		field = $(this);
		clearTimeout(hours_timer);
		hours_timer = setTimeout(function() {
			hours_timer = null;
			hours_blur(field);
		}, 750);
	});
}

function hours_blur(field) {
	clearTimeout(hours_timer);
	task = field.parents('tr.task');
    field = $('input', field.parents('.hours'));
	val = parseInt(field.val(), 10);
	if(hours_cache < 0) {
		console.log("Problem blurring hours field; hours cache is unset");
	} else if(val != hours_cache) {
		save_task(task, 'hours', val);
		set_status(task, val == 0 ? 'complete' : 'in progress');
	}
}

function setup_search() {
	$('input#search').keydown(function(e) {
		if(e.keyCode == 13) {
			document.location = '/sprints/' + sprintid + '?search=' + encodeURIComponent($(this).val());
		}
	});
}

function setup_filter_buttons() {
	$.each(['#filter-assigned', '#filter-status'], function(_, selector) {
		$(selector + ' a:gt(0)').click(function(e) {
			if(e.ctrlKey) {
				$(this).toggleClass('selected');
			} else {
				$(selector + ' a').removeClass('selected');
				$(this).addClass('selected');
			}

			apply_filters();
			return false;
		});

		$(selector + ' a:first').click(function(e) {
			$(selector + ' a').removeClass('selected');
			apply_filters();
			return false;
		});
	});
}

function setup_group_arrows() {
	$('tr.group img').click(function(e) {
		switch($(this).attr('src')) {
		case '/static/images/collapse.png':
			$(this).attr('src', '/static/images/expand.png');
			groupid=$(this).parents('tr').attr('groupid');
			$('tr.task[groupid=' + groupid + ']').hide();
			break;
		case '/static/images/expand.png':
			$(this).attr('src', '/static/images/collapse.png');
			groupid=$(this).parents('tr').attr('groupid');
			$('tr.task[groupid=' + groupid + ']').show();
			apply_filters();
			break;
		}
	});
}

function setup_bugzilla(tasks) {
	tasks.each(function() {
		name = $('td.name span', $(this)).text();
		link = $('td.actions a.bugzilla', $(this));

		//TODO Support multiple bugs in one task
		/*
		re = /(?:bug |bz)([0-9]+)/gi;
		while(match = re.exec(name)) {
			id = parseInt(match[1], 10);
		}
		*/

		if(match = name.match(/(?:bug |bz)([0-9]+)/i)) {
			id = parseInt(match[1], 10);
			link.attr('href', 'http://bugs.arxandefense.com/show_bug.cgi?id=' + id);
			link.show();
		} else {
			link.hide();
		}
	});
}

function apply_filters() {
	assigned = $('#filter-assigned a.selected');
	statuses = $('#filter-status a.selected');
	tasks = $('tr.task');

	tasks.show();

	if(assigned.length > 0) {
		$('#filter-assigned a:not(.selected)').each(function() {
			tasks.filter('[assigned="' + $(this).attr('assigned') + '"]').hide();
		});
	}

	if(statuses.length > 0) {
		$('#filter-status a:not(.selected)').each(function() {
			tasks.filter('[status="' + $(this).attr('status') + '"]').hide();
		});
	}

	$('tr.group img[src="/static/images/expand.png"]').each(function(e) {
		groupid=$(this).parents('tr').attr('groupid');
		$('tr.task[groupid=' + groupid + ']').hide();
	});
}

function fancy_cells(table_selector) {
	$(table_selector).sortable({
		items: 'tr:not(.nodrag)',
		containment: table_selector,
		start: function(event, ui) {
			row = $(ui.item[0]);
			if(row.hasClass('group')) { // Moving a group
				$('tr.task', $(table_selector)).addClass('hide-temp');
			} else if(row.hasClass('task')) { // Moving a task
			}
		},
		stop: function(event, ui) {
			row = $(ui.item[0]);
			$('tr.task.hide-temp').removeClass('hide-temp');
			// row.addClass('dirty');
			if(row.hasClass('group')) {
				// Move all the group's tasks under the group header row
				$('tr.group').each(function() {
					groupid = $(this).attr('groupid');
					$('tr.task[groupid=' + groupid + ']').insertAfter($(this));
				});

				//TODO Save new group position
				unimplemented('Group move');
			} else if(row.hasClass('task')) {
				new_group = row.prevAll('tr.group');
				new_group_id = new_group.length ? new_group.attr('groupid') : 0;
				row.attr('groupid', new_group_id);

				pred = row.prev();
				if(!pred.length) { // First row in the table
					save_task(row, 'taskmove', ':0');
				} else if(pred.hasClass('task')) { // Inserted after a task
					save_task(row, 'taskmove', pred.attr('taskid'));
				} else if(pred.hasClass('group')) { // Inserted after a group header (top of the group)
					//TODO Save
					save_task(row, 'taskmove', ':' + new_group_id);
				} else {
					//FAIL
				}
			}
		},
	});

	// $(table_selector).tableDnD({onDragClass: 'dragging'});
	// return;
	$('td.name > span', $(table_selector)).editable({
		onSubmit: function(c) {
			id = $(this).attr('id').replace('name_span_', '');
			$('[name="name[' + id + ']"]').val(c.current);
			if(c.previous != c.current) {
				task = $(this).parents('tr.task');
				// dirty($(this));
				setup_bugzilla(task);
				save_task(task, 'name', c.current);
			}
		}
	});

	$('td.assigned > span', $(table_selector)).click(function() {
		task = $(this).parents('tr.task');
		sel = $('<select/>');
		for(i in usernames) {
			opt = $("<option>").text(usernames[i]).attr('selected', usernames[i] == task.attr('assigned'));
			sel.append(opt);
		}
		$(this).replaceWith(sel);
		// sel.chosen();
		sel.change(function() {
			task.attr('assigned', $(this).val());
			save_task(task, 'assigned', $(this).val());
		});
	});

	$('tr.task img.status', $(table_selector)).contextMenu({
		menu: 'status-menu'
	}, function(action, el, pos) {
		task = $(el).parents('tr.task');
		set_status(task, action);
	});

	$('tr.task img.goal', $(table_selector)).contextMenu({
		menu: 'goal-menu'
	}, function(action, el, pos) {
		task = $(el).parents('tr.task');
		id = $(el).attr('id').replace('goal_', '');
		field = $('[name="goal[' + id + ']"]');
		if(field.val() != action) {
			task.attr('goal', action);
			field.val(action);
			$(el).attr('src', goal_imgs[action]);
			$(el).attr('title', goal_texts[action]);
			save_task(task, 'goal', action);
		}
	});
}

function set_status(task, status_name) {
	node = $('img.status', task);
	id = node.attr('id').replace('status_', '');
	field = $('[name="status[' + id + ']"]');
	if(field.val() != status_name) {
		task.attr('status', status_name);
		field.val(status_name);
		node.attr('src', '/static/images/status-' + status_name.replace(' ', '-') + '.png');
		node.attr('title', status_texts[status_name]);
		save_task(task, 'status', status_name);
		if(['canceled', 'deferred', 'split'].indexOf(status_name) >= 0) {
			$('td.hours input', task).val('0');
			save_task(task, 'hours', 0);
		}
	}
}

savingMutex = false;
function save_task(task, field, value, counter) {
	console.log("Saving change to " + task.attr('taskid') + "(" + task.attr('revid') + "): " + field + " <- " + value + " (attempt " + (counter == undefined ? 0 : counter) + ")");
	$('.saving', task).css('visibility', 'visible');

	if(savingMutex) {
		if(counter == 10) {
			box = $('#post-status');
			box.attr('class', 'alert-message error');
			$('span.boxbody', box).html("Timed out trying to set task " + task.attr('taskid') + " " + field + " to " + value);
			showbox(box);
			$('.saving', task).css('visibility', 'hidden');
		} else {
			setTimeout(function() {save_task(task, field, value, (counter == undefined ? 0 : counter) + 1);}, 200);
		}
		return;
	}

	savingMutex = true;
	$.post("/sprints/" + sprintid, {'id': task.attr('taskid'), 'rev_id': task.attr('revid'), 'field': field, 'value': value}, function(data, text, request) {
		box = $('#post-status')
		switch(request.status) {
		case 200:
			box.attr('class', 'alert-message error');
			$('span.boxbody', box).html(data);
			break;
		case 298:
			box.attr('class', 'alert-message warning');
			$('span.boxbody', box).html(data);
			break;
		case 299:
			$('tr.task[taskid=' + task.attr('taskid') + ']').attr('revid', data);
			console.log("Changed saved; new revision is " + data);
			box.fadeOut();
			box = null;
			break;
		default:
			box.attr('class', 'alert-message success');
			$('span.boxbody', box).html("Unexpected response code " + request.status)
			break;
		}

		if(box) {
			showbox(box);
		}
		$('.saving', task).css('visibility', 'hidden');
		savingMutex = false;
	});
}

function delete_task(task_id) {
	row = $('tr.task[taskid=' + task_id + ']');
	if(row) {
		save_task(row, 'deleted', 1);
		row.fadeOut();
	}
}

function unimplemented(what) {
	box = $('#post-status');
	box.attr('class', 'alert-message warning');
	$('span.boxbody', box).html("Unimplemented: " + what);
	showbox(box);
}<commit_msg>Forgot to skip changing the status on hours blur during planning<commit_after>$(document).ready(function() {
	// TODO if grouping stays disabled, this can be pulled back out of a function
	fancy_cells('#all-tasks.editable');

	setup_hours_events();
	setup_search();
	setup_filter_buttons();
	setup_group_arrows();
	setup_bugzilla($('tr.task'));

	$('#post-status').hide();
	$('.saving').css('visibility', 'hidden');
});

hours_cache = -1;
hours_timer = null;
function setup_hours_events() {
	$('td.hours img').css('opacity', 0);

	$('td.hours').hover(
		function() {
			// $('img', this).fadeIn();
			$('img', this).animate({opacity: 1});
		},
		function() {
			// $('img', this).fadeOut();
			$('img', this).animate({opacity: 0});
		}
	);

	$('td.hours img').hover(
		function() {
			$(this).attr('src', $(this).attr('src').match(/[^\.]+/) + '-lit.png');
		},
		function() {
			$(this).attr('src', $(this).attr('src').replace('-lit', ''));
		}
	);

	$('td.hours img').click(function() {
		task = $(this).parents('tr.task');
		field = $('input', $(this).parents('.hours'));
		old_val = val = parseInt(field.val(), 10);
		val += parseInt($(this).attr('amt'), 10);
		if(val < 0) {
			if(old_val == 0) {
				return;
			} else {
				val = 0;
			}
		}
		field.val('' + val)
		save_task(task, 'hours', val);
		if(!isPlanning) {
			set_status(task, val == 0 ? 'complete' : 'in progress');
		}
	});

	$("td.hours input").focus(function(event) {
		hours_cache = parseInt($('input', $(this).parents('.hours')).val(), 10);
	}).blur(function(event) {
		hours_blur($(this));
	}).keypress(function(event) {
		field = $(this);
		clearTimeout(hours_timer);
		hours_timer = setTimeout(function() {
			hours_timer = null;
			hours_blur(field);
		}, 750);
	});
}

function hours_blur(field) {
	clearTimeout(hours_timer);
	task = field.parents('tr.task');
	field = $('input', field.parents('.hours'));
	val = parseInt(field.val(), 10);
	if(hours_cache < 0) {
		console.log("Problem blurring hours field; hours cache is unset");
	} else if(val != hours_cache) {
		save_task(task, 'hours', val);
		if(!isPlanning) {
			set_status(task, val == 0 ? 'complete' : 'in progress');
		}
	}
}

function setup_search() {
	$('input#search').keydown(function(e) {
		if(e.keyCode == 13) {
			document.location = '/sprints/' + sprintid + '?search=' + encodeURIComponent($(this).val());
		}
	});
}

function setup_filter_buttons() {
	$.each(['#filter-assigned', '#filter-status'], function(_, selector) {
		$(selector + ' a:gt(0)').click(function(e) {
			if(e.ctrlKey) {
				$(this).toggleClass('selected');
			} else {
				$(selector + ' a').removeClass('selected');
				$(this).addClass('selected');
			}

			apply_filters();
			return false;
		});

		$(selector + ' a:first').click(function(e) {
			$(selector + ' a').removeClass('selected');
			apply_filters();
			return false;
		});
	});
}

function setup_group_arrows() {
	$('tr.group img').click(function(e) {
		switch($(this).attr('src')) {
		case '/static/images/collapse.png':
			$(this).attr('src', '/static/images/expand.png');
			groupid=$(this).parents('tr').attr('groupid');
			$('tr.task[groupid=' + groupid + ']').hide();
			break;
		case '/static/images/expand.png':
			$(this).attr('src', '/static/images/collapse.png');
			groupid=$(this).parents('tr').attr('groupid');
			$('tr.task[groupid=' + groupid + ']').show();
			apply_filters();
			break;
		}
	});
}

function setup_bugzilla(tasks) {
	tasks.each(function() {
		name = $('td.name span', $(this)).text();
		link = $('td.actions a.bugzilla', $(this));

		//TODO Support multiple bugs in one task
		/*
		re = /(?:bug |bz)([0-9]+)/gi;
		while(match = re.exec(name)) {
			id = parseInt(match[1], 10);
		}
		*/

		if(match = name.match(/(?:bug |bz)([0-9]+)/i)) {
			id = parseInt(match[1], 10);
			link.attr('href', 'http://bugs.arxandefense.com/show_bug.cgi?id=' + id);
			link.show();
		} else {
			link.hide();
		}
	});
}

function apply_filters() {
	assigned = $('#filter-assigned a.selected');
	statuses = $('#filter-status a.selected');
	tasks = $('tr.task');

	tasks.show();

	if(assigned.length > 0) {
		$('#filter-assigned a:not(.selected)').each(function() {
			tasks.filter('[assigned="' + $(this).attr('assigned') + '"]').hide();
		});
	}

	if(statuses.length > 0) {
		$('#filter-status a:not(.selected)').each(function() {
			tasks.filter('[status="' + $(this).attr('status') + '"]').hide();
		});
	}

	$('tr.group img[src="/static/images/expand.png"]').each(function(e) {
		groupid=$(this).parents('tr').attr('groupid');
		$('tr.task[groupid=' + groupid + ']').hide();
	});
}

function fancy_cells(table_selector) {
	$(table_selector).sortable({
		items: 'tr:not(.nodrag)',
		containment: table_selector,
		start: function(event, ui) {
			row = $(ui.item[0]);
			if(row.hasClass('group')) { // Moving a group
				$('tr.task', $(table_selector)).addClass('hide-temp');
			} else if(row.hasClass('task')) { // Moving a task
			}
		},
		stop: function(event, ui) {
			row = $(ui.item[0]);
			$('tr.task.hide-temp').removeClass('hide-temp');
			// row.addClass('dirty');
			if(row.hasClass('group')) {
				// Move all the group's tasks under the group header row
				$('tr.group').each(function() {
					groupid = $(this).attr('groupid');
					$('tr.task[groupid=' + groupid + ']').insertAfter($(this));
				});

				//TODO Save new group position
				unimplemented('Group move');
			} else if(row.hasClass('task')) {
				new_group = row.prevAll('tr.group');
				new_group_id = new_group.length ? new_group.attr('groupid') : 0;
				row.attr('groupid', new_group_id);

				pred = row.prev();
				if(!pred.length) { // First row in the table
					save_task(row, 'taskmove', ':0');
				} else if(pred.hasClass('task')) { // Inserted after a task
					save_task(row, 'taskmove', pred.attr('taskid'));
				} else if(pred.hasClass('group')) { // Inserted after a group header (top of the group)
					//TODO Save
					save_task(row, 'taskmove', ':' + new_group_id);
				} else {
					//FAIL
				}
			}
		},
	});

	// $(table_selector).tableDnD({onDragClass: 'dragging'});
	// return;
	$('td.name > span', $(table_selector)).editable({
		onSubmit: function(c) {
			id = $(this).attr('id').replace('name_span_', '');
			$('[name="name[' + id + ']"]').val(c.current);
			if(c.previous != c.current) {
				task = $(this).parents('tr.task');
				// dirty($(this));
				setup_bugzilla(task);
				save_task(task, 'name', c.current);
			}
		}
	});

	$('td.assigned > span', $(table_selector)).click(function() {
		task = $(this).parents('tr.task');
		sel = $('<select/>');
		for(i in usernames) {
			opt = $("<option>").text(usernames[i]).attr('selected', usernames[i] == task.attr('assigned'));
			sel.append(opt);
		}
		$(this).replaceWith(sel);
		// sel.chosen();
		sel.change(function() {
			task.attr('assigned', $(this).val());
			save_task(task, 'assigned', $(this).val());
		});
	});

	$('tr.task img.status', $(table_selector)).contextMenu({
		menu: 'status-menu'
	}, function(action, el, pos) {
		task = $(el).parents('tr.task');
		set_status(task, action);
	});

	$('tr.task img.goal', $(table_selector)).contextMenu({
		menu: 'goal-menu'
	}, function(action, el, pos) {
		task = $(el).parents('tr.task');
		id = $(el).attr('id').replace('goal_', '');
		field = $('[name="goal[' + id + ']"]');
		if(field.val() != action) {
			task.attr('goal', action);
			field.val(action);
			$(el).attr('src', goal_imgs[action]);
			$(el).attr('title', goal_texts[action]);
			save_task(task, 'goal', action);
		}
	});
}

function set_status(task, status_name) {
	node = $('img.status', task);
	id = node.attr('id').replace('status_', '');
	field = $('[name="status[' + id + ']"]');
	if(field.val() != status_name) {
		task.attr('status', status_name);
		field.val(status_name);
		node.attr('src', '/static/images/status-' + status_name.replace(' ', '-') + '.png');
		node.attr('title', status_texts[status_name]);
		save_task(task, 'status', status_name);
		if(['canceled', 'deferred', 'split'].indexOf(status_name) >= 0) {
			$('td.hours input', task).val('0');
			save_task(task, 'hours', 0);
		}
	}
}

savingMutex = false;
function save_task(task, field, value, counter) {
	console.log("Saving change to " + task.attr('taskid') + "(" + task.attr('revid') + "): " + field + " <- " + value + " (attempt " + (counter == undefined ? 0 : counter) + ")");
	$('.saving', task).css('visibility', 'visible');

	if(savingMutex) {
		if(counter == 10) {
			box = $('#post-status');
			box.attr('class', 'alert-message error');
			$('span.boxbody', box).html("Timed out trying to set task " + task.attr('taskid') + " " + field + " to " + value);
			showbox(box);
			$('.saving', task).css('visibility', 'hidden');
		} else {
			setTimeout(function() {save_task(task, field, value, (counter == undefined ? 0 : counter) + 1);}, 200);
		}
		return;
	}

	savingMutex = true;
	$.post("/sprints/" + sprintid, {'id': task.attr('taskid'), 'rev_id': task.attr('revid'), 'field': field, 'value': value}, function(data, text, request) {
		box = $('#post-status')
		switch(request.status) {
		case 200:
			box.attr('class', 'alert-message error');
			$('span.boxbody', box).html(data);
			break;
		case 298:
			box.attr('class', 'alert-message warning');
			$('span.boxbody', box).html(data);
			break;
		case 299:
			$('tr.task[taskid=' + task.attr('taskid') + ']').attr('revid', data);
			console.log("Changed saved; new revision is " + data);
			box.fadeOut();
			box = null;
			break;
		default:
			box.attr('class', 'alert-message success');
			$('span.boxbody', box).html("Unexpected response code " + request.status)
			break;
		}

		if(box) {
			showbox(box);
		}
		$('.saving', task).css('visibility', 'hidden');
		savingMutex = false;
	});
}

function delete_task(task_id) {
	row = $('tr.task[taskid=' + task_id + ']');
	if(row) {
		save_task(row, 'deleted', 1);
		row.fadeOut();
	}
}

function unimplemented(what) {
	box = $('#post-status');
	box.attr('class', 'alert-message warning');
	$('span.boxbody', box).html("Unimplemented: " + what);
	showbox(box);
}<|endoftext|>


<commit_before>var Duplex = require('stream').Duplex || require('readable-stream').Duplex;
var EventEmitter = require('events').EventEmitter;
var bitfield = require('bitfield');
var util = require('util');
var bncode = require('bncode');

var MESSAGE_PROTOCOL     = new Buffer([0x13,0x42,0x69,0x74,0x54,0x6f,0x72,0x72,0x65,0x6e,0x74,0x20,0x70,0x72,0x6f,0x74,0x6f,0x63,0x6f,0x6c]);
var MESSAGE_KEEP_ALIVE   = new Buffer([0x00,0x00,0x00,0x00]);
var MESSAGE_CHOKE        = new Buffer([0x00,0x00,0x00,0x01,0x00]);
var MESSAGE_UNCHOKE      = new Buffer([0x00,0x00,0x00,0x01,0x01]);
var MESSAGE_INTERESTED   = new Buffer([0x00,0x00,0x00,0x01,0x02]);
var MESSAGE_UNINTERESTED = new Buffer([0x00,0x00,0x00,0x01,0x03]);
var MESSAGE_RESERVED     = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
var MESSAGE_PORT         = [0x00,0x00,0x00,0x03,0x09,0x00,0x00];

var noop = function() {};

var pull = function(requests, piece, offset, length) {
	for (var i = 0; i < requests.length; i++) {
		var req = requests[i];
		if (req.piece !== piece || req.offset !== offset || req.length !== length) continue;
		if (i === 0) requests.shift();
		else requests.splice(i, 1);
		return req;
	}
	return null;
};

var Request = function(piece, offset, length, callback) {
	this.piece = piece;
	this.offset = offset;
	this.length = length;
	this.callback = callback;
	this.timeout = null;
};

var Wire = function() {
	if (!(this instanceof Wire)) return new Wire();
	Duplex.call(this);
	var self = this;

	this.amChoking = true;
	this.amInterested = false;
	this.peerChoking = true;
	this.peerInterested = false;
	this.peerPieces = [];
	this.peerExtensions = {};
	this.peerAddress = null; // external
	this.peerSpeed = null;   // external

	this.uploaded = 0;
	this.downloaded = 0;

	this.requests = [];
	this.peerRequests = [];

	this._keepAlive = null;
	this._finished = false;

	this.on('finish', function() {
		self._finished = true;
		self.push(null); // cannot be half open
		clearInterval(self._keepAlive);
		self._parse(Number.MAX_VALUE, noop);
		while (self.peerRequests.length) self.peerRequests.pop();
		while (self.requests.length) self._callback(self.requests.shift(), new Error('wire is closed'), null);
	});

	var ontimeout = function() {
		self._callback(self.requests.shift(), new Error('request has timed out'), null);
		self.emit('timeout');
	};

	this._timeout = 0;
	this._ontimeout = ontimeout;

	var onmessagelength = function(buffer) {
		var length = buffer.readUInt32BE(0);
		if (length) return self._parse(length, onmessage);
		self._parse(4, onmessagelength);
		self.emit('keep-alive');
	};

	var onmessage = function(buffer) {
		self._parse(4, onmessagelength);
		switch (buffer[0]) {
			case 0:
			return self._onchoke();
			case 1:
			return self._onunchoke();
			case 2:
			return self._oninterested();
			case 3:
			return self._onuninterested();
			case 4:
			return self._onhave(buffer.readUInt32BE(1));
			case 5:
			return self._onbitfield(buffer.slice(1));
			case 6:
			return self._onrequest(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 7:
			return self._onpiece(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.slice(9));
			case 8:
			return self._oncancel(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 9:
			return self._onport(buffer.readUInt16BE(1));
			case 20:
			return self._onextended(bncode.decode(buffer));
		}
		self.emit('unknownmessage', buffer);
	};

	this._buffer = [];
	this._bufferSize = 0;
	this._parser = null;
	this._parserSize = 0;

	this._parse(1, function(buffer) {
		var pstrlen = buffer.readUInt8(0);
		self._parse(pstrlen + 48, function(handshake) {
			handshake = handshake.slice(pstrlen);
			self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {dht: !!(handshake[7] & 1)});
			self._parse(4, onmessagelength);
		});
	});
};

util.inherits(Wire, Duplex);

Wire.prototype.handshake = function(infoHash, peerId, extensions) {
	if (typeof infoHash === 'string') infoHash = new Buffer(infoHash, 'hex');
	if (typeof peerId === 'string') peerId = new Buffer(peerId);
	if (infoHash.length !== 20 || peerId.length !== 20) throw new Error('infoHash and peerId MUST have length 20');

	var reserved = new Buffer(MESSAGE_RESERVED);
	if (extensions && extensions.dht) reserved[7] |= 1;
	reserved[5] |= 0x10; // enable extended message

	this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId], MESSAGE_PROTOCOL.length+48));
};

Wire.prototype.choke = function() {
	if (this.amChoking) return;
	this.amChoking = true;
	while (self.peerRequests.length) self.peerRequests.pop();
	this._push(MESSAGE_CHOKE);
};

Wire.prototype.unchoke = function() {
	if (!this.amChoking) return;
	this.amChoking = false;
	this._push(MESSAGE_UNCHOKE);
};

Wire.prototype.interested = function() {
	if (this.amInterested) return;
	this.amInterested = true;
	this._push(MESSAGE_INTERESTED);
};

Wire.prototype.uninterested = function() {
	if (!this.amInterested) return;
	this.amInterested = false;
	this._push(MESSAGE_UNINTERESTED);
};

Wire.prototype.have = function(i) {
	this._message(4, [i], null);
};

Wire.prototype.bitfield = function(bitfield) {
	if (bitfield.buffer) bitfield = bitfield.buffer; // support bitfield objects
	this._message(5, [], bitfield);
};

Wire.prototype.request = function(i, offset, length, callback) {
	if (!callback) callback = noop;
	if (this._finished)   return callback(new Error('wire is closed'));
	if (this.peerChoking) return callback(new Error('peer is choking'));
	this.requests.push(new Request(i, offset, length, callback));
	this._updateTimeout();
	this._message(6, [i, offset, length], null);
};

Wire.prototype.piece = function(i, offset, buffer) {
	this.uploaded += buffer.length;
	this.emit('upload', buffer.length);
	this._message(7, [i, offset], buffer);
};

Wire.prototype.cancel = function(i, offset, length) {
	this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null);
	this._message(8, [i, offset, length], null);
};

Wire.prototype.port = function(port) {
	var message = new Buffer(MESSAGE_PORT);
	message.writeUInt16BE(port, 5);
	this._push(message);
};

Wire.prototype.setKeepAlive = function(bool) {
	clearInterval(this._keepAlive);
	if (bool === false) return;
	this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000);
};

Wire.prototype.setTimeout = function(ms, fn) {
	if (this.requests.length) clearTimeout(this.requests[0].timeout);
	this._timeout = ms;
	this._updateTimeout();
	if (fn) this.on('timeout', fn);
};

Wire.prototype.destroy = function() {
	this.emit('close');
	this.end();
};

// inbound

Wire.prototype._onhandshake = function(infoHash, peerId, extensions) {
	this.peerExtensions = extensions;
	this.emit('handshake', infoHash, peerId, extensions);
};

Wire.prototype._oninterested = function() {
	this.peerInterested = true;
	this.emit('interested');
};

Wire.prototype._onuninterested = function() {
	this.peerInterested = false;
	this.emit('uninterested');
};

Wire.prototype._onchoke = function() {
	this.peerChoking = true;
	this.emit('choke');
	while (this.requests.length) this._callback(this.requests.shift(), new Error('peer is choking'), null);
};

Wire.prototype._onunchoke = function() {
	this.peerChoking = false;
	this.emit('unchoke');
};

Wire.prototype._onbitfield = function(buffer) {
	var pieces = bitfield(buffer);
	for (var i = 0; i < 8 * buffer.length; i++) {
		this.peerPieces[i] = pieces.get(i);
	}
	this.emit('bitfield', buffer);
};

Wire.prototype._onhave = function(i) {
	this.peerPieces[i] = true;
	this.emit('have', i);
};

Wire.prototype._onrequest = function(i, offset, length) {
	if (this.amChoking) return;

	var self = this;
	var respond = function(err, buffer) {
		if (err || request !== pull(self.peerRequests, i, offset, length)) return;
		self.piece(i, offset, buffer);
	};

	var request = new Request(i, offset, length, respond);
	this.peerRequests.push(request);
	this.emit('request', i, offset, length, respond);
};

Wire.prototype._oncancel = function(i, offset, length) {
	pull(this.peerRequests, i, offset, length);
	this.emit('cancel', i, offset, length);
};

Wire.prototype._onpiece = function(i, offset, buffer) {
	this._callback(pull(this.requests, i, offset, buffer.length), null, buffer);
	this.downloaded += buffer.length;
	this.emit('download', buffer.length);
	this.emit('piece', i, offset, buffer);
};

Wire.prototype._onport = function(port) {
	this.emit('port', port);
};

Wire.prototype._onextended = function(ext) {
	this.emit('extended', ext);
};

// helpers and streams

Wire.prototype._callback = function(request, err, buffer) {
	if (!request) return;
	if (request.timeout) clearTimeout(request.timeout);
	if (!this.peerChoking && !this._finished) this._updateTimeout();
	request.callback(err, buffer);
};

Wire.prototype._updateTimeout = function() {
	if (!this._timeout || !this.requests.length || this.requests[0].timeout) return;
	this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout);
};

Wire.prototype._message = function(id, numbers, data) {
	var dataLength = data ? data.length : 0;
	var buffer = new Buffer(5 + 4 * numbers.length);

	buffer.writeUInt32BE(buffer.length + dataLength - 4, 0);
	buffer[4] = id;
	for (var i = 0; i < numbers.length; i++) {
		buffer.writeUInt32BE(numbers[i], 5 + 4 * i);
	}

	this._push(buffer);
	if (data) this._push(data);
};

Wire.prototype._push = function(data) {
	if (this._finished) return;
	this.push(data);
};

Wire.prototype._parse = function(size, parser) {
	this._parserSize = size;
	this._parser = parser;
};

Wire.prototype._write = function(data, encoding, callback) {
	this._bufferSize += data.length;
	this._buffer.push(data);

	while (this._bufferSize >= this._parserSize) {
		var buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer, this._bufferSize);
		this._bufferSize -= this._parserSize;
		this._buffer = this._bufferSize ? [buffer.slice(this._parserSize)] : [];
		this._parser(buffer.slice(0, this._parserSize));
	}

	callback();
};

Wire.prototype._read = noop;

module.exports = Wire;
<commit_msg>send extended messages<commit_after>var Duplex = require('stream').Duplex || require('readable-stream').Duplex;
var EventEmitter = require('events').EventEmitter;
var bitfield = require('bitfield');
var util = require('util');
var bncode = require('bncode');

var MESSAGE_PROTOCOL     = new Buffer([0x13,0x42,0x69,0x74,0x54,0x6f,0x72,0x72,0x65,0x6e,0x74,0x20,0x70,0x72,0x6f,0x74,0x6f,0x63,0x6f,0x6c]);
var MESSAGE_KEEP_ALIVE   = new Buffer([0x00,0x00,0x00,0x00]);
var MESSAGE_CHOKE        = new Buffer([0x00,0x00,0x00,0x01,0x00]);
var MESSAGE_UNCHOKE      = new Buffer([0x00,0x00,0x00,0x01,0x01]);
var MESSAGE_INTERESTED   = new Buffer([0x00,0x00,0x00,0x01,0x02]);
var MESSAGE_UNINTERESTED = new Buffer([0x00,0x00,0x00,0x01,0x03]);
var MESSAGE_RESERVED     = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00];
var MESSAGE_PORT         = [0x00,0x00,0x00,0x03,0x09,0x00,0x00];

var noop = function() {};

var pull = function(requests, piece, offset, length) {
	for (var i = 0; i < requests.length; i++) {
		var req = requests[i];
		if (req.piece !== piece || req.offset !== offset || req.length !== length) continue;
		if (i === 0) requests.shift();
		else requests.splice(i, 1);
		return req;
	}
	return null;
};

var Request = function(piece, offset, length, callback) {
	this.piece = piece;
	this.offset = offset;
	this.length = length;
	this.callback = callback;
	this.timeout = null;
};

var Wire = function() {
	if (!(this instanceof Wire)) return new Wire();
	Duplex.call(this);
	var self = this;

	this.amChoking = true;
	this.amInterested = false;
	this.peerChoking = true;
	this.peerInterested = false;
	this.peerPieces = [];
	this.peerExtensions = {};
	this.peerAddress = null; // external
	this.peerSpeed = null;   // external

	this.uploaded = 0;
	this.downloaded = 0;

	this.requests = [];
	this.peerRequests = [];

	this._keepAlive = null;
	this._finished = false;

	this.on('finish', function() {
		self._finished = true;
		self.push(null); // cannot be half open
		clearInterval(self._keepAlive);
		self._parse(Number.MAX_VALUE, noop);
		while (self.peerRequests.length) self.peerRequests.pop();
		while (self.requests.length) self._callback(self.requests.shift(), new Error('wire is closed'), null);
	});

	var ontimeout = function() {
		self._callback(self.requests.shift(), new Error('request has timed out'), null);
		self.emit('timeout');
	};

	this._timeout = 0;
	this._ontimeout = ontimeout;

	var onmessagelength = function(buffer) {
		var length = buffer.readUInt32BE(0);
		if (length) return self._parse(length, onmessage);
		self._parse(4, onmessagelength);
		self.emit('keep-alive');
	};

	var onmessage = function(buffer) {
		self._parse(4, onmessagelength);
		switch (buffer[0]) {
			case 0:
			return self._onchoke();
			case 1:
			return self._onunchoke();
			case 2:
			return self._oninterested();
			case 3:
			return self._onuninterested();
			case 4:
			return self._onhave(buffer.readUInt32BE(1));
			case 5:
			return self._onbitfield(buffer.slice(1));
			case 6:
			return self._onrequest(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 7:
			return self._onpiece(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.slice(9));
			case 8:
			return self._oncancel(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 9:
			return self._onport(buffer.readUInt16BE(1));
			case 20:
			return self._onextended(bncode.decode(buffer));
		}
		self.emit('unknownmessage', buffer);
	};

	this._buffer = [];
	this._bufferSize = 0;
	this._parser = null;
	this._parserSize = 0;

	this._parse(1, function(buffer) {
		var pstrlen = buffer.readUInt8(0);
		self._parse(pstrlen + 48, function(handshake) {
			handshake = handshake.slice(pstrlen);
			self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {dht: !!(handshake[7] & 1)});
			self._parse(4, onmessagelength);
		});
	});
};

util.inherits(Wire, Duplex);

Wire.prototype.handshake = function(infoHash, peerId, extensions) {
	if (typeof infoHash === 'string') infoHash = new Buffer(infoHash, 'hex');
	if (typeof peerId === 'string') peerId = new Buffer(peerId);
	if (infoHash.length !== 20 || peerId.length !== 20) throw new Error('infoHash and peerId MUST have length 20');

	var reserved = new Buffer(MESSAGE_RESERVED);
	if (extensions && extensions.dht) reserved[7] |= 1;
	reserved[5] |= 0x10; // enable extended message

	this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId], MESSAGE_PROTOCOL.length+48));
};

Wire.prototype.choke = function() {
	if (this.amChoking) return;
	this.amChoking = true;
	while (self.peerRequests.length) self.peerRequests.pop();
	this._push(MESSAGE_CHOKE);
};

Wire.prototype.unchoke = function() {
	if (!this.amChoking) return;
	this.amChoking = false;
	this._push(MESSAGE_UNCHOKE);
};

Wire.prototype.interested = function() {
	if (this.amInterested) return;
	this.amInterested = true;
	this._push(MESSAGE_INTERESTED);
};

Wire.prototype.uninterested = function() {
	if (!this.amInterested) return;
	this.amInterested = false;
	this._push(MESSAGE_UNINTERESTED);
};

Wire.prototype.have = function(i) {
	this._message(4, [i], null);
};

Wire.prototype.bitfield = function(bitfield) {
	if (bitfield.buffer) bitfield = bitfield.buffer; // support bitfield objects
	this._message(5, [], bitfield);
};

Wire.prototype.request = function(i, offset, length, callback) {
	if (!callback) callback = noop;
	if (this._finished)   return callback(new Error('wire is closed'));
	if (this.peerChoking) return callback(new Error('peer is choking'));
	this.requests.push(new Request(i, offset, length, callback));
	this._updateTimeout();
	this._message(6, [i, offset, length], null);
};

Wire.prototype.piece = function(i, offset, buffer) {
	this.uploaded += buffer.length;
	this.emit('upload', buffer.length);
	this._message(7, [i, offset], buffer);
};

Wire.prototype.cancel = function(i, offset, length) {
	this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null);
	this._message(8, [i, offset, length], null);
};

Wire.prototype.extended = function(msg) {
	this._message(20, [bncode.encode(msg)], null);
};

Wire.prototype.port = function(port) {
	var message = new Buffer(MESSAGE_PORT);
	message.writeUInt16BE(port, 5);
	this._push(message);
};

Wire.prototype.setKeepAlive = function(bool) {
	clearInterval(this._keepAlive);
	if (bool === false) return;
	this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000);
};

Wire.prototype.setTimeout = function(ms, fn) {
	if (this.requests.length) clearTimeout(this.requests[0].timeout);
	this._timeout = ms;
	this._updateTimeout();
	if (fn) this.on('timeout', fn);
};

Wire.prototype.destroy = function() {
	this.emit('close');
	this.end();
};

// inbound

Wire.prototype._onhandshake = function(infoHash, peerId, extensions) {
	this.peerExtensions = extensions;
	this.emit('handshake', infoHash, peerId, extensions);
};

Wire.prototype._oninterested = function() {
	this.peerInterested = true;
	this.emit('interested');
};

Wire.prototype._onuninterested = function() {
	this.peerInterested = false;
	this.emit('uninterested');
};

Wire.prototype._onchoke = function() {
	this.peerChoking = true;
	this.emit('choke');
	while (this.requests.length) this._callback(this.requests.shift(), new Error('peer is choking'), null);
};

Wire.prototype._onunchoke = function() {
	this.peerChoking = false;
	this.emit('unchoke');
};

Wire.prototype._onbitfield = function(buffer) {
	var pieces = bitfield(buffer);
	for (var i = 0; i < 8 * buffer.length; i++) {
		this.peerPieces[i] = pieces.get(i);
	}
	this.emit('bitfield', buffer);
};

Wire.prototype._onhave = function(i) {
	this.peerPieces[i] = true;
	this.emit('have', i);
};

Wire.prototype._onrequest = function(i, offset, length) {
	if (this.amChoking) return;

	var self = this;
	var respond = function(err, buffer) {
		if (err || request !== pull(self.peerRequests, i, offset, length)) return;
		self.piece(i, offset, buffer);
	};

	var request = new Request(i, offset, length, respond);
	this.peerRequests.push(request);
	this.emit('request', i, offset, length, respond);
};

Wire.prototype._oncancel = function(i, offset, length) {
	pull(this.peerRequests, i, offset, length);
	this.emit('cancel', i, offset, length);
};

Wire.prototype._onpiece = function(i, offset, buffer) {
	this._callback(pull(this.requests, i, offset, buffer.length), null, buffer);
	this.downloaded += buffer.length;
	this.emit('download', buffer.length);
	this.emit('piece', i, offset, buffer);
};

Wire.prototype._onport = function(port) {
	this.emit('port', port);
};

Wire.prototype._onextended = function(ext) {
	this.emit('extended', ext);
};

// helpers and streams

Wire.prototype._callback = function(request, err, buffer) {
	if (!request) return;
	if (request.timeout) clearTimeout(request.timeout);
	if (!this.peerChoking && !this._finished) this._updateTimeout();
	request.callback(err, buffer);
};

Wire.prototype._updateTimeout = function() {
	if (!this._timeout || !this.requests.length || this.requests[0].timeout) return;
	this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout);
};

Wire.prototype._message = function(id, numbers, data) {
	var dataLength = data ? data.length : 0;
	var buffer = new Buffer(5 + 4 * numbers.length);

	buffer.writeUInt32BE(buffer.length + dataLength - 4, 0);
	buffer[4] = id;
	for (var i = 0; i < numbers.length; i++) {
		buffer.writeUInt32BE(numbers[i], 5 + 4 * i);
	}

	this._push(buffer);
	if (data) this._push(data);
};

Wire.prototype._push = function(data) {
	if (this._finished) return;
	this.push(data);
};

Wire.prototype._parse = function(size, parser) {
	this._parserSize = size;
	this._parser = parser;
};

Wire.prototype._write = function(data, encoding, callback) {
	this._bufferSize += data.length;
	this._buffer.push(data);

	while (this._bufferSize >= this._parserSize) {
		var buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer, this._bufferSize);
		this._bufferSize -= this._parserSize;
		this._buffer = this._bufferSize ? [buffer.slice(this._parserSize)] : [];
		this._parser(buffer.slice(0, this._parserSize));
	}

	callback();
};

Wire.prototype._read = noop;

module.exports = Wire;
<|endoftext|>


<commit_before>define(["js/ui/View", "js/core/Content", "js/ui/Button", "underscore"], function (View, Content, Button, _) {

    return View.inherit("js.ui.MenuButtonClass", {
        defaults: {
            /***
             * The label of the button.
             *
             * @type String
             */
            label: "",
            componentClass: 'btn-group menu-button',
            /**
             * The class name of the menu.
             *
             * @type String
             */
            menuClassName: "dropdown-menu",
            /**
             * Set's the menu visible.
             *
             * @type Boolean
             */
            menuVisible: false,
            /**
             * The class of the inner span in the link element
             *
             * @type String
             */
            labelClass: "",

            /**
             * The class of the inner link element
             *
             * @type String
             */
            buttonClass: "",

            /***
             * the class name for the icon
             * @type String
             */
            iconClass: null,

            /***
             * The inner label for the icon
             *
             * @type String
             */
            iconLabel: ""

        },
        $defaultContentName: 'menu',

        $instances: [],

        addChild: function (child) {
            this.callBase();
            if (child instanceof Button) {
                this._collectButton(child);
            }
        },

        _collectButton: function (child) {
            this.$button = child;
            this.$toggleButton = child;
        },

        _renderType: function (type) {
            this.$button.set({type: type});
        },

        _renderIconClass: function (iconClass) {
            this.$button.set({iconClass: iconClass});
        },

        _renderLabel: function (label) {
            if (this.$button) {
                this.$button.set({label: label});
            }

        },

        _renderMenuVisible: function (visible) {
            if (visible === true) {
                for (var i = 0; i < this.$instances.length; i++) {
                    if (this.$instances[i] != this) {
                        this.$instances[i].set({menuVisible: false});
                    }
                }
                this.addClass('open');
            } else {
                this.removeClass('open');
            }

        },

        _bindDomEvents: function (el) {
            this.callBase();

            if (!_.contains(this.$instances, this)) {
                this.$instances.push(this);
            }
            var self = this;

            this.bindDomEvent('click', function () {
                self.set({menuVisible: false});
            });

            this.$toggleButton.bind('on:click', function (e) {
                if (self.$.enabled) {
                    e.stopPropagation();
                    e.preventDefault();
                    self.set({menuVisible: !self.$.menuVisible});
                }
            });

            this.$button.bind('on:click', function (e) {
                self.trigger('on:click', e, self);
            });

            this.dom(this.$stage.$document).bindDomEvent('click', function (e) {
                self.set({menuVisible: false});
            });

        },

        _preventDefault: function (e) {
            e.$.stopPropagation();
        },
        /***
         * Closes the menu
         */
        closeMenu: function () {
            this.set('menuVisible', false);
        },

        /**
         * Opens the menu
         */
        openMenu: function () {
            this.set('menuVisible', true);
        },

        /***
         * Toggles the menu
         */
        toggleMenu: function () {
            this.set('menuVisible', !this.$.menuVisible);
        }
    });

});<commit_msg>fixed MenuButton buttonClass<commit_after>define(["js/ui/View", "js/core/Content", "js/ui/Button", "underscore"], function (View, Content, Button, _) {

    return View.inherit("js.ui.MenuButtonClass", {
        defaults: {
            /***
             * The label of the button.
             *
             * @type String
             */
            label: "",
            componentClass: 'btn-group menu-button',
            /**
             * The class name of the menu.
             *
             * @type String
             */
            menuClassName: "dropdown-menu",
            /**
             * Set's the menu visible.
             *
             * @type Boolean
             */
            menuVisible: false,
            /**
             * The class of the inner span in the link element
             *
             * @type String
             */
            labelClass: "",

            /**
             * The class of the inner link element
             *
             * @type String
             */
            buttonClass: "btn",

            /***
             * the class name for the icon
             * @type String
             */
            iconClass: null,

            /***
             * The inner label for the icon
             *
             * @type String
             */
            iconLabel: ""

        },
        $defaultContentName: 'menu',

        $instances: [],

        addChild: function (child) {
            this.callBase();
            if (child instanceof Button) {
                this._collectButton(child);
            }
        },

        _collectButton: function (child) {
            this.$button = child;
            this.$toggleButton = child;
        },

        _renderType: function (type) {
            this.$button.set({type: type});
        },

        _renderIconClass: function (iconClass) {
            this.$button.set({iconClass: iconClass});
        },

        _renderLabel: function (label) {
            if (this.$button) {
                this.$button.set({label: label});
            }

        },

        _renderMenuVisible: function (visible) {
            if (visible === true) {
                for (var i = 0; i < this.$instances.length; i++) {
                    if (this.$instances[i] != this) {
                        this.$instances[i].set({menuVisible: false});
                    }
                }
                this.addClass('open');
            } else {
                this.removeClass('open');
            }

        },

        _bindDomEvents: function (el) {
            this.callBase();

            if (!_.contains(this.$instances, this)) {
                this.$instances.push(this);
            }
            var self = this;

            this.bindDomEvent('click', function () {
                self.set({menuVisible: false});
            });

            this.$toggleButton.bind('on:click', function (e) {
                if (self.$.enabled) {
                    e.stopPropagation();
                    e.preventDefault();
                    self.set({menuVisible: !self.$.menuVisible});
                }
            });

            this.$button.bind('on:click', function (e) {
                self.trigger('on:click', e, self);
            });

            this.dom(this.$stage.$document).bindDomEvent('click', function (e) {
                self.set({menuVisible: false});
            });

        },

        _preventDefault: function (e) {
            e.$.stopPropagation();
        },
        /***
         * Closes the menu
         */
        closeMenu: function () {
            this.set('menuVisible', false);
        },

        /**
         * Opens the menu
         */
        openMenu: function () {
            this.set('menuVisible', true);
        },

        /***
         * Toggles the menu
         */
        toggleMenu: function () {
            this.set('menuVisible', !this.$.menuVisible);
        }
    });

});<|endoftext|>


<commit_before>require('dotenv').config({ silent: true });

const port = process.env.PORT || 3000;
const app = require('./config/application');

/**
 * Initializing server
 */
app.listen(port, '0.0.0.0', err => {
  if (err) {
    console.error(err);
  }
  console.info('==> 🌎 Listening on http://0.0.0.0:%s/', port);
});
<commit_msg>eslint fix for server<commit_after>require('dotenv').config({ silent: true });

const port = process.env.PORT || 3000;
const app = require('./config/application');

/**
 * Initializing server
 */
app.listen(port, '0.0.0.0', (err) => {
  if (err) console.error(err);
  console.info('==> 🌎 Listening on http://0.0.0.0:%s/', port);
});
<|endoftext|>


<commit_before>/*
 * auto-reload.js
 * https://github.com/savetheinternet/Tinyboard/blob/master/js/auto-reload.js
 *
 * Brings AJAX to Tinyboard.
 *
 * Released under the MIT license
 * Copyright (c) 2012 Michael Save <savetheinternet@tinyboard.org>
 *
 * Usage:
 *   $config['additional_javascript'][] = 'js/jquery.min.js';
 *   $config['additional_javascript'][] = 'js/settings.js';
 *   $config['additional_javascript'][] = 'js/auto-reload.js';
 *
 */

$(document).ready(function(){
	if($('div.banner').length == 0)
		return; // not index

	if($(".post.op").size() != 1)
		return; //not thread page

	var poll_interval;
	if ((settings.updateFrequency <= 10) || (typeof settings.updateFrequency == 'undefined')) {
		settings.updateFrequency = 10;
	}
	var poll_accuracy = settings.updateFrequency * 1000;
    var updateGrowl;
    var connectionGrowl;
    var manualUpdate = false;

	var poll = function() {
		$.ajax({
			url: document.location,
            data: {nocache: Math.random()},
			beforeSend: function() {
                $('#updateThread i').addClass('fa-spin');
                if (settings.growlEnabled) {
                    if (manualUpdate && $("[data-growl=container]").length != 0) {
                        updateGrowl.update('message', 'Продолжаем обновление...');
                    } else updateGrowl = $.growl({
                        message: _('Обновление...')
                    }, {
                        delay: 0,
                        allow_dismiss: false
                    });
                }
            },
            error: function(xhr, status, error) {
                if (settings.growlEnabled) {
                    connectionGrowl = $.growl({
                        title: "" + _('Соединение потеряно!') + "
",
                        message: _('Не удалось получить новые посты.')
                    }, {
                        type: "warning"
                    });
                }
            },
			success: function(data) {
                if (settings.updateThread && settings.growlEnabled) {
                    updateGrowl.close();
                }
				$(data).find('div.post.reply').each(function() {
					var id = $(this).attr('id');
					if($('#' + id).length == 0) {
						$(this).insertAfter($('div.post:not(.hover):not(.post-hover):last').next()).after('
');
                        if (settings.useAnimateCSS) {
                            $(this).addClass('animated fadeIn');
                        }
						$(document).trigger('new_post', this);
						$('#updateThread i').removeClass('fa-spin');
					}
                    if (settings.useMomentJS) {
                        now = moment();
                        momentize(document);
                    }
				});
			}
		});
		(settings.updateThread == "false") ? poll_interval = false : poll_interval = setTimeout(poll, poll_accuracy);
	};

    (settings.simpleNavbar) ? $("#navigation").prepend(' ') :
        (device_type == "mobile") ? $("#navigation").prepend(' ') :
            $("#navigation").prepend(' ' + _('Обновить') + ' ');

	function pollNewPosts() {
		clearTimeout(poll_interval);
		poll_interval = setTimeout(poll, poll_accuracy);
	}

	if(settings.updateThread) {
        pollNewPosts();
	}

	$('#updateThread').click(function () {
		$('#updateThread i').addClass('fa-spin');
		poll_interval = setTimeout(poll, 1000);
        manualUpdate = true;
		$(document).bind('new_post', function(e, post) {
			clearTimeout(poll_interval);
            updateGrowl.close();
        });
	});
});

<commit_msg>auto-reload.js: improved auto-update and on-click update, now they suck less<commit_after>/*
 * auto-reload.js
 * https://github.com/savetheinternet/Tinyboard/blob/master/js/auto-reload.js
 *
 * Brings AJAX to Tinyboard.
 *
 * Released under the MIT license
 * Copyright (c) 2012 Michael Save <savetheinternet@tinyboard.org>
 *
 * Usage:
 *   $config['additional_javascript'][] = 'js/jquery.min.js';
 *   $config['additional_javascript'][] = 'js/settings.js';
 *   $config['additional_javascript'][] = 'js/auto-reload.js';
 *
 */

$(document).ready(function(){
	if($('div.banner').length == 0)
		return; // not index

	if($(".post.op").size() != 1)
		return; //not thread page

	if ((settings.updateFrequency <= 10) || (typeof settings.updateFrequency == 'undefined')) {
		settings.updateFrequency = 10;
	}
	var poll_accuracy = settings.updateFrequency * 1000;
    var updateGrowl;
    var connectionGrowl;

	var poll = function() {
		$.ajax({
			url: document.location,
            data: {nocache: Math.random()},
			beforeSend: function() {
                $('#updateThread i').addClass('fa-spin');
                if (settings.growlEnabled) {
                    updateGrowl = $.growl({
                        message: _('Обновление...')
                    }, {
                        delay: 0,
                        allow_dismiss: false
                    });
                }
            },
            error: function(xhr, status, error) {
                if (settings.growlEnabled) {
                    connectionGrowl = $.growl({
                        title: "" + _('Соединение потеряно!') + "
",
                        message: _('Не удалось получить новые посты.')
                    }, {
                        type: "warning"
                    });
                }
            },
			success: function(data) {
                if (settings.updateThread && settings.growlEnabled) {
                    updateGrowl.close();
                }
				$(data).find('div.post.reply').each(function() {
					var id = $(this).attr('id');
					if($('#' + id).length == 0) {
						$(this).insertAfter($('div.post:not(.hover):not(.post-hover):last').next()).after('
');
                        if (settings.useAnimateCSS) {
                            $(this).addClass('animated fadeIn');
                        }
						$(document).trigger('new_post', this);
					}
                    if (settings.useMomentJS) {
                        now = moment();
                        momentize(document);
                    }
				});
                $('#updateThread i').removeClass('fa-spin');
                updateGrowl.close();
			}
		});
	};

    (settings.simpleNavbar) ? $("#navigation").prepend(' ') :
        (device_type == "mobile") ? $("#navigation").prepend(' ') :
            $("#navigation").prepend(' ' + _('Обновить') + ' ');

	function pollNewPosts() {
        setInterval(poll, poll_accuracy);
	}

	if(settings.updateThread) {
        pollNewPosts();
	}

	$('#updateThread').click(function () {
        poll();
	});
});

<|endoftext|>


<commit_before>/*jslint node: true */
var events = require('events');
var util = require('util');
var debug = require('debug')('http-shutdown');

module.exports = function () {
  var app = new App();
  return {
    middleware: function () {
      return app.middleware();
    },

    attach: function (server) {
      return app.attach(server);
    },

    destroy: function (cb) {
      return app.destroy(cb);
    }
  };
};

function Tracker() {
  events.EventEmitter.call(this);
  this.responses = [];
  this.connections = [];
}
util.inherits(Tracker, events.EventEmitter);

Tracker.prototype.createMiddleware = function createMiddleware() {
  var tracker = this;
  return function (req, res, next) {
    tracker.track(req, res, next);
  };
};

Tracker.prototype.track = function track(req, res, next) {
  this.watchForResponseToComplete(res);
  this.watchForConnectionToClose(req.socket);
  next();
};

Tracker.prototype.watchForResponseToComplete = function watchForResponseToComplete(res) {
  var tracker = this,
      responses = this.responses;

  if (responses.indexOf(res) !== -1) {
    return;
  }

  responses.push(res);

  res.on('finish', function () {
    var index = responses.indexOf(res);

    debug('A response completed');
    if (index !== -1) {
      responses.splice(index, 1);
    }

    if (responses.length === 0) {
      debug('tracker emitting finish');
      tracker.emit('finish');
    }
  });
};

Tracker.prototype.watchForConnectionToClose = function (socket) {
  var tracker = this,
      connections = this.connections;

  if (connections.indexOf(socket) !== -1) {
    return;
  }

  connections.push(socket);

  socket.on('close', function () {
    var index = connections.indexOf(socket);

    debug('A connection closed');

    if (index !== -1) {
      connections.splice(index, 1);
    }
  });
};

Tracker.prototype.pendingResponses = function () {
  return this.responses.length;
};

Tracker.prototype.closeAllConnections = function () {
  debug('Tracker closeAllConnections');
  this.connections.forEach(function (socket) {
    debug('Tracker closing a socket...');
    socket.setKeepAlive(false);
    socket.end();
    socket.unref();
  });
  this.connections.splice(0, this.connections.length);
};

function App() {
  var thisApp = this;

  this.tracker = new Tracker();
  this.connections = [];
  this.tracker.on('finish', function() {
    debug('tracker finish caught');
    thisApp.tryShutdown();
  });
}

App.prototype.tryShutdown = function() {
  debug('checking for shutdown conditions');
  if (this.closed && this.tracker.pendingResponses() === 0) {
    debug('auth app is closed and no responses pending. Shutting server down.');
    this.tracker.closeAllConnections();
    this.server.unref();
    this.destroyCB();
  }
};

App.prototype.attach = function(server) {
  this.server = server;
  thisApp = this;

  server.on('connection', function (socket) {
    debug('server has a new connection', socket.remoteAddress, socket.remotePort);
    thisApp.trackConnection(socket);
  });
  server.on('close', function (event) {
    debug('caught close event', event);
  });
};

App.prototype.middleware = function() {
  return this.tracker.createMiddleware();
};

App.prototype.trackConnection = function (socket) {
  this.connections.push(socket);
};

App.prototype.destroy = function (cb) {
  debug('App destroy');
  this.destroyCB = cb;
  if (!this.closed) {
    this.closed = true;
    this.server.close();
    this.tryShutdown();
  }
};
<commit_msg>Collapse classes.<commit_after>/*jslint node: true */
var events = require('events');
var util = require('util');
var debug = require('debug')('http-shutdown');

module.exports = function () {
  var app = new Tracker();
  return {
    middleware: function () {
      return app.middleware();
    },

    attach: function (server) {
      return app.attach(server);
    },

    destroy: function (cb) {
      return app.destroy(cb);
    }
  };
};

function Tracker() {
  events.EventEmitter.call(this);
  this.responses = [];
  this.connections = [];
  var thisTracker = this;

  this.on('finish', function() {
    debug('tracker finish caught');
    thisTracker.tryShutdown();
  });
}
util.inherits(Tracker, events.EventEmitter);

Tracker.prototype.middleware = function() {
  var tracker = this;
  return function (req, res, next) {
    tracker.track(req, res, next);
  };
};

Tracker.prototype.track = function track(req, res, next) {
  this.watchForResponseToComplete(res);
  this.watchForConnectionToClose(req.socket);
  next();
};

Tracker.prototype.watchForResponseToComplete = function watchForResponseToComplete(res) {
  var tracker = this,
      responses = this.responses;

  if (responses.indexOf(res) !== -1) {
    return;
  }

  responses.push(res);

  res.on('finish', function () {
    var index = responses.indexOf(res);

    debug('A response completed');
    if (index !== -1) {
      responses.splice(index, 1);
    }

    if (responses.length === 0) {
      debug('tracker emitting finish');
      tracker.emit('finish');
    }
  });
};

Tracker.prototype.watchForConnectionToClose = function (socket) {
  var tracker = this,
      connections = this.connections;

  if (connections.indexOf(socket) !== -1) {
    return;
  }

  connections.push(socket);

  socket.on('close', function () {
    var index = connections.indexOf(socket);

    debug('A connection closed');

    if (index !== -1) {
      connections.splice(index, 1);
    }
  });
};

Tracker.prototype.pendingResponses = function () {
  return this.responses.length;
};

Tracker.prototype.closeAllConnections = function () {
  debug('Tracker closeAllConnections');
  this.connections.forEach(function (socket) {
    debug('Tracker closing a socket...');
    socket.setKeepAlive(false);
    socket.end();
    socket.unref();
  });
  this.connections.splice(0, this.connections.length);
};

Tracker.prototype.tryShutdown = function() {
  debug('checking for shutdown conditions');
  if (this.closed && this.pendingResponses() === 0) {
    debug('auth app is closed and no responses pending. Shutting server down.');
    this.closeAllConnections();
    this.server.unref();
    this.destroyCB();
  }
};

Tracker.prototype.attach = function(server) {
  this.server = server;
  thisTracker = this;

  server.on('connection', function (socket) {
    debug('server has a new connection', socket.remoteAddress, socket.remotePort);
    thisTracker.trackConnection(socket);
  });
  server.on('close', function (event) {
    debug('caught close event', event);
  });
};

Tracker.prototype.trackConnection = function (socket) {
  this.connections.push(socket);
};

Tracker.prototype.destroy = function (cb) {
  debug('Tracker destroy');
  this.destroyCB = cb;
  if (!this.closed) {
    this.closed = true;
    this.server.close();
    this.tryShutdown();
  }
};
<|endoftext|>