<commit_before>/*

.form-tabs        <- data-init-tab = default tab
  .nav-tabs
     .li a ...    <- click this
  .tab-pane ...   <- to find these
     .form-group  <- and swap these, so only one exists at at a time

Both "a" and ".tab-pane" should be marked with matching data-tab attrs.

*/

window.form_tabs_init = function (tabs_div) {
    var top  = $(tabs_div);
    var val0 = top.data('init-tab');
    var tabs = {};

    function show_tab(tab) {
        top.find('.nav-tabs a').each(function (_ii, lnk) {
            var this_tab = $(lnk).data('tab');
            var item = $(lnk).closest('li');

            if (tab == this_tab) {
                item.addClass('active');
            }
            else {
                item.removeClass('active');
            }
        });

        top.find('.tab-pane').each(function (_ii, div) {
            var this_tab = $(div).data('tab');
            var $div = $(div);

            if (tab == this_tab) {
                $div.addClass('active');
                $div.show();
                if (tabs[this_tab]) {
                    $div.append(tabs[tab]);
                }
            }
            else {
                var fg = $div.find('.form-group');
                if (fg[0]) {
                    tabs[this_tab] = fg[0];
                    $(fg[0]).detach();
                }
                $div.removeClass('active');
                $div.hide();
            }
        });
    }

    // Match height before we hide anything.
    var panes = top.find('.tab-pane');
    panes.matchHeight({byRow: false, property: 'height'});

    top.find(".nav-tabs a").each(function(_ii, lnk) {
        var tab = $(lnk).data('tab');
        if (tab == val0) {
            show_tab(tab);
        }

        $(lnk).on("click", function(evt) {
            evt.preventDefault();
            show_tab(tab);
        });
    });

};

window.form_tabs_init_all = function (thing) {
    $(thing).find('.form-tabs').each(function (_ii, el) {
        form_tabs_init(el);
    });
};

$(function() {
    form_tabs_init_all('body');
});

<commit_msg>js issues<commit_after>/*

.form-tabs        <- data-init-tab = default tab
  .nav-tabs
     .li a ...    <- click this
  .tab-pane ...   <- to find these
     .form-group  <- and swap these, so only one exists at at a time

Both "a" and ".tab-pane" should be marked with matching data-tab attrs.

*/

window.form_tabs_init = function (tabs_div) {
    var top  = $(tabs_div);
    var val0 = top.data('init-tab');
    var tabs = {};

    function show_tab(tab) {
        top.find('.nav-tabs a').each(function (_ii, lnk) {
            var this_tab = $(lnk).data('tab');
            var item = $(lnk).closest('li');

            if (tab == this_tab) {
                item.addClass('active');
            }
            else {
                item.removeClass('active');
            }
        });

        top.find('.tab-pane').each(function (_ii, div) {
            var this_tab = $(div).data('tab');
            var $div = $(div);

            if (tab == this_tab) {
                $div.addClass('active');
                $div.show();
                if (tabs[this_tab]) {
                    $div.append(tabs[tab]);
                }
            }
            else {
                var fg = $div.find('.form-group');
                if (fg[0]) {
                    tabs[this_tab] = fg[0];
                    $(fg[0]).detach();
                }
                $div.removeClass('active');
                $div.hide();
            }
        });
    }

    // Match height before we hide anything.
    var panes = top.find('.tab-pane');
    panes.matchHeight({byRow: false, property: 'height'});
    // matchHeight will trigger on onload and on window-resize
    // so be sure to refill and unhide all the inert tabs before trying to
    // recompute their heights
    var oldBefore = $.fn.matchHeight._beforeUpdate;
    var oldActive = null;
    $.fn.matchHeight._beforeUpdate = function(e, groups) {
      oldActive = top.find('.tab-pane.active');
      panes.each(function(_, div) {
        $(div).addClass('active').append(tabs[$(div).data('tab')]).show();
      });
      if (oldBefore)
        oldBefore(e, groups);
    }
    var oldAfter = $.fn.matchHeight._afterUpdate;
    $.fn.matchHeight._afterUpdate = function(e, groups) {
      if (oldAfter)
        oldAfter(e, groups);
      oldActive.each(function(_, div) {
        show_tab($(div).data('tab'));
      });
    }

    top.find(".nav-tabs a").each(function(_ii, lnk) {
        var tab = $(lnk).data('tab');
        if (tab == val0) {
            show_tab(tab);
        }

        $(lnk).on("click", function(evt) {
            evt.preventDefault();
            show_tab(tab);
        });
    });

};

window.form_tabs_init_all = function (thing) {
    $(thing).find('.form-tabs').each(function (_ii, el) {
        form_tabs_init(el);
    });
};

$(function() {
    form_tabs_init_all('body');
});

<|endoftext|>


<commit_before>import Vue from 'vue';
import { mapActions } from 'vuex';
import Translate from '~/vue_shared/translate';
import ide from './components/ide.vue';
import store from './stores';
import router from './ide_router';
import { parseBoolean } from '../lib/utils/common_utils';

Vue.use(Translate);

/**
 * Initialize the IDE on the given element.
 *
 * @param {Element} el - The element that will contain the IDE.
 * @param {Object} options - Extra options for the IDE (Used by EE).
 * @param {(e:Element) => Object} options.extraInitialData -
 *   Function that returns extra properties to seed initial data.
 * @param {Component} options.rootComponent -
 *   Component that overrides the root component.
 */
export function initIde(el, options = {}) {
  if (!el) return null;

  const { extraInitialData = () => ({}), rootComponent = ide } = options;

  return new Vue({
    el,
    store,
    router,
    created() {
      this.setEmptyStateSvgs({
        emptyStateSvgPath: el.dataset.emptyStateSvgPath,
        noChangesStateSvgPath: el.dataset.noChangesStateSvgPath,
        committedStateSvgPath: el.dataset.committedStateSvgPath,
        pipelinesEmptyStateSvgPath: el.dataset.pipelinesEmptyStateSvgPath,
        promotionSvgPath: el.dataset.promotionSvgPath,
      });
      this.setLinks({
        ciHelpPagePath: el.dataset.ciHelpPagePath,
        webIDEHelpPagePath: el.dataset.webIdeHelpPagePath,
      });
      this.setInitialData({
        clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
        ...extraInitialData(el),
      });
    },
    methods: {
      ...mapActions(['setEmptyStateSvgs', 'setLinks', 'setInitialData']),
    },
    render(createElement) {
      return createElement(rootComponent);
    },
  });
}

// tell webpack to load assets from origin so that web workers don't break
export function resetServiceWorkersPublicPath() {
  // __webpack_public_path__ is a global variable that can be used to adjust
  // the webpack publicPath setting at runtime.
  // see: https://webpack.js.org/guides/public-path/
  const relativeRootPath = (gon && gon.relative_url_root) || '';
  const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
  __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
}

/**
 * Start the IDE.
 *
 * @param {Objects} options - Extra options for the IDE (Used by EE).
 */
export function startIde(options) {
  document.addEventListener('DOMContentLoaded', () => {
    const ideElement = document.getElementById('ide');
    if (ideElement) {
      resetServiceWorkersPublicPath();
      initIde(ideElement, options);
    }
  });
}
<commit_msg>Update IDE index.js with 'extendStore'<commit_after>import Vue from 'vue';
import { mapActions } from 'vuex';
import _ from 'underscore';
import Translate from '~/vue_shared/translate';
import ide from './components/ide.vue';
import store from './stores';
import router from './ide_router';
import { parseBoolean } from '../lib/utils/common_utils';

Vue.use(Translate);

/**
 * Initialize the IDE on the given element.
 *
 * @param {Element} el - The element that will contain the IDE.
 * @param {Object} options - Extra options for the IDE (Used by EE).
 * @param {Component} options.rootComponent -
 *   Component that overrides the root component.
 * @param {(store:Vuex.Store, el:Element) => Vuex.Store} options.extendStore -
 *   Function that receives the default store and returns an extended one.
 */
export function initIde(el, options = {}) {
  if (!el) return null;

  const { rootComponent = ide, extendStore = _.identity } = options;

  return new Vue({
    el,
    store: extendStore(store, el),
    router,
    created() {
      this.setEmptyStateSvgs({
        emptyStateSvgPath: el.dataset.emptyStateSvgPath,
        noChangesStateSvgPath: el.dataset.noChangesStateSvgPath,
        committedStateSvgPath: el.dataset.committedStateSvgPath,
        pipelinesEmptyStateSvgPath: el.dataset.pipelinesEmptyStateSvgPath,
        promotionSvgPath: el.dataset.promotionSvgPath,
      });
      this.setLinks({
        ciHelpPagePath: el.dataset.ciHelpPagePath,
        webIDEHelpPagePath: el.dataset.webIdeHelpPagePath,
      });
      this.setInitialData({
        clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
      });
    },
    methods: {
      ...mapActions(['setEmptyStateSvgs', 'setLinks', 'setInitialData']),
    },
    render(createElement) {
      return createElement(rootComponent);
    },
  });
}

// tell webpack to load assets from origin so that web workers don't break
export function resetServiceWorkersPublicPath() {
  // __webpack_public_path__ is a global variable that can be used to adjust
  // the webpack publicPath setting at runtime.
  // see: https://webpack.js.org/guides/public-path/
  const relativeRootPath = (gon && gon.relative_url_root) || '';
  const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
  __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
}

/**
 * Start the IDE.
 *
 * @param {Objects} options - Extra options for the IDE (Used by EE).
 */
export function startIde(options) {
  document.addEventListener('DOMContentLoaded', () => {
    const ideElement = document.getElementById('ide');
    if (ideElement) {
      resetServiceWorkersPublicPath();
      initIde(ideElement, options);
    }
  });
}
<|endoftext|>


<commit_before>jQuery(document).ready(function ($) {

    /*
     * Mobile Data Adjustment
     */
    var running = false;
    $('table.mobile-data').each(function () {
        var count = $(this).find($('table.mobile-data tr')).length - 1;
        $(this).append('');
        $(this).append('');
        $(this).append('1/' + count + '');
    });
    $('table.mobile-data > span.left').click(function () {

        if (running === true)
            return;

        running = true;

        var table = $(this).parent('table').children('tbody'),
            current = table.find('tr:first-of-type'),
            next = table.find('tr:last-of-type'),
            page = table.children('span.center').children('span.current'),
            number = parseInt(page.html()),
            count = table.find($('table.mobile-data tr')).length - 1;

        next.insertBefore(current);
        number--;
        if (number <= 0) {
            number = count;
        }
        page.html(number);

        fixCellHeight(table);

        running = false;

    });
    $('table.mobile-data > span.right').click(function () {

        if (running === true)
            return;

        running = true;

        var table = $(this).parent('table').children('tbody'),
            current = table.find('tr:first-of-type'),
            last = table.find('tr:last-of-type'),
            page = table.children('span.center').children('span.current'),
            number = parseInt(page.html()),
            count = table.find($('table.mobile-data tr')).length - 1;

        current.insertAfter(last);
        number++;
        if (number > count) {
            number = 1;
        }
        page.html(number);

        fixCellHeight(table);

        running = false;

    });

    function fixCellHeight(table) {

        table.find('tr:first-of-type').children('th').each(function (i) {
            i++;
            var brother = table.find('tr:nth-of-type(2)').children('td:nth-of-type(' + i + ')');

            if ($(this).height() > brother.height()) {
                brother.css('height', $(this).outerHeight() + 'px');
            } else {
                $(this).css('height', brother.outerHeight() + 'px');
            }

        });

    }

    if ($(window).width() <= 768) {
        $('table.mobile-data').each(function () {
            fixCellHeight($(this));
        });
    }

    /*
     * Toggler activation
     */
    $('.toggler .first-line').click(function (e) {
        e.preventDefault();
        $(this).parent('.toggler').toggleClass('toggled');
    });
    $('.toggler .first-line').on('click', 'a', function (e) {
        e.stopPropagation();
        return true;
    });

})
<commit_msg>possible fix for links in toggle<commit_after>jQuery(document).ready(function ($) {

    /*
     * Mobile Data Adjustment
     */
    var running = false;
    $('table.mobile-data').each(function () {
        var count = $(this).find($('table.mobile-data tr')).length - 1;
        $(this).append('');
        $(this).append('');
        $(this).append('1/' + count + '');
    });
    $('table.mobile-data > span.left').click(function () {

        if (running === true)
            return;

        running = true;

        var table = $(this).parent('table').children('tbody'),
            current = table.find('tr:first-of-type'),
            next = table.find('tr:last-of-type'),
            page = table.children('span.center').children('span.current'),
            number = parseInt(page.html()),
            count = table.find($('table.mobile-data tr')).length - 1;

        next.insertBefore(current);
        number--;
        if (number <= 0) {
            number = count;
        }
        page.html(number);

        fixCellHeight(table);

        running = false;

    });
    $('table.mobile-data > span.right').click(function () {

        if (running === true)
            return;

        running = true;

        var table = $(this).parent('table').children('tbody'),
            current = table.find('tr:first-of-type'),
            last = table.find('tr:last-of-type'),
            page = table.children('span.center').children('span.current'),
            number = parseInt(page.html()),
            count = table.find($('table.mobile-data tr')).length - 1;

        current.insertAfter(last);
        number++;
        if (number > count) {
            number = 1;
        }
        page.html(number);

        fixCellHeight(table);

        running = false;

    });

    function fixCellHeight(table) {

        table.find('tr:first-of-type').children('th').each(function (i) {
            i++;
            var brother = table.find('tr:nth-of-type(2)').children('td:nth-of-type(' + i + ')');

            if ($(this).height() > brother.height()) {
                brother.css('height', $(this).outerHeight() + 'px');
            } else {
                $(this).css('height', brother.outerHeight() + 'px');
            }

        });

    }

    if ($(window).width() <= 768) {
        $('table.mobile-data').each(function () {
            fixCellHeight($(this));
        });
    }

    /*
     * Toggler activation
     */
    $('.toggler .first-line').click( function(e){
        if (! ($(e.target).is('a') || $(e.target).parent().is('a'))) {
            e.preventDefault();
            $(this).parent('.toggler').toggleClass('toggled');
        }
    });

})
<|endoftext|>


<commit_before>"use strict";

var timeoutLength = 120000;
var intervalLength = 60000;

var timeoutID = -1;
var intervalID = -1;

var clockDisplay = document.getElementById("display");
var clockScreen = document.getElementById("clock-screen");

function padTimeunit(tunit) {
    if (tunit < 10) {
        return "0" + tunit;
    }

    return tunit;
}

function updateClockDisplay() {
    var currentTime = new Date();
    var timeString =
        padTimeunit(currentTime.getHours()) + ":" +
        padTimeunit(currentTime.getMinutes());

    clockDisplay.innerHTML = timeString;
}

function primeClockScreen() {
    clearTimeout(timeoutID);
    clearInterval(intervalID);

    clockScreen.style.zIndex = -1;

    timeoutID = setTimeout(initClockScreen, timeoutLength);
}

function initClockScreen() {
    updateClockDisplay();
    intervalID = setInterval(updateClockDisplay, intervalLength);
    clockScreen.style.zIndex = 1;
}

function reloadIfClockActive() {
    if(clockScreen.style.zIndex == 1) {
        location.reload();
    } else {
        primeClockScreen();
    }
}

window.onload = primeClockScreen;
window.onclick = prineClockScreen; //reloadIfClockActive;
<commit_msg>skrivefeil<commit_after>"use strict";

var timeoutLength = 120000;
var intervalLength = 60000;

var timeoutID = -1;
var intervalID = -1;

var clockDisplay = document.getElementById("display");
var clockScreen = document.getElementById("clock-screen");

function padTimeunit(tunit) {
    if (tunit < 10) {
        return "0" + tunit;
    }

    return tunit;
}

function updateClockDisplay() {
    var currentTime = new Date();
    var timeString =
        padTimeunit(currentTime.getHours()) + ":" +
        padTimeunit(currentTime.getMinutes());

    clockDisplay.innerHTML = timeString;
}

function primeClockScreen() {
    clearTimeout(timeoutID);
    clearInterval(intervalID);

    clockScreen.style.zIndex = -1;

    timeoutID = setTimeout(initClockScreen, timeoutLength);
}

function initClockScreen() {
    updateClockDisplay();
    intervalID = setInterval(updateClockDisplay, intervalLength);
    clockScreen.style.zIndex = 1;
}

function reloadIfClockActive() {
    if(clockScreen.style.zIndex == 1) {
        location.reload();
    } else {
        primeClockScreen();
    }
}

window.onload = primeClockScreen;
window.onclick = primeClockScreen; //reloadIfClockActive;
<|endoftext|>


<commit_before>document.addEventListener("DOMContentLoaded",function(evt){
    var formElement = document.getElementById("js-upload-form");
    var uploadedFiles = document.getElementById("js-upload-files");

    formElement.addEventListener("submit", function(e){
	if (e.preventDefault) e.preventDefault();
	var fd = new FormData();

	for (var i = 0; i < uploadedFiles.files.length; i++){
	    fd.append("media",uploadedFiles.files[i]);
	    var request = new XMLHttpRequest();
	    request.open("POST", "/admin/media/create");
	    request.send(fd);

	    request.onreadystatechange = function(){
		if(request.readyState == 4 && request.status == 200){
		    var file = JSON.parse(request.responseText);
		    var element = document.createElement('div');
		    var insert = document.createElement('a');
		    insert.appendChild(document.createTextNode("Insert"));
		    var del = document.createElement('a');
		    del.appendChild(document.createTextNode("Delete"));
		    insert.addEventListener('click', function(){
			switch(file.Type){
			case 'image/png':
			case 'image/jpeg':
			case 'image/gif':
			    insertImage(file);
			    break;
			case 'application/x-download':
			case 'application/x-rar-compressed':
			case 'application/x-zip-compressed':
			    insertDownloadLink(file);
			    break;
			case 'video/webm':
			    insertVideo(file);
			    break;
			}
		    });
		    del.addEventListener('click',function(){
			deleteFile(file);
		    });

		    element.setAttribute('class', 'list-group-item list-group-item-success');
		    element.setAttribute('id', 'file-'+file.Id);
		    insert.setAttribute('class', 'pull-right btn btn-sm btn-primary');
		    del.setAttribute('class', 'pull-right btn btn-sm btn-default');
		    element.appendChild(document.createTextNode(file.Filename));
		    element.appendChild(insert);
		    element.appendChild(del);

		    var uploadedFilesList = document.getElementById('uploadedFiles');
		    uploadedFilesList.insertBefore(element,uploadedFilesList.firstChild);
		}

	    }
	}

	return false;
    });

});

function deleteFile(file){
    var request = new XMLHttpRequest();
    request.open("GET", "/admin/media/delete/" + file.Id);
    request.send();
    request.onreadystatechange = function(){
	if(request.readyState == 4 && request.status == 200){
	    var element = document.getElementById("file-"+file.Id);
	    element.parentNode.removeChild(element);

	}
    }
}

function insertImage(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '![example '+file.Filename+'](/static/uploads/'+file.Filename+' )';
    insertAtCursor(contentArea,text);
}

function insertDownloadLink(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '[ '+file.Filename+'](/static/uploads/'+file.Filename+' )';
    insertAtCursor(contentArea,text);
}
function insertVideo(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '\<video src="/static/uploads/'+file.Filename+'" controls>Your browser does not support the video element.</video>';
    insertAtCursor(contentArea,text);
}

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;

    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
	myField.selectionStart = startPos + myValue.length;
	myField.selectionEnd = startPos + myValue.length;
    } else {
        myField.value += myValue;
    }
}

<commit_msg>fit width by default<commit_after>document.addEventListener("DOMContentLoaded",function(evt){
    var formElement = document.getElementById("js-upload-form");
    var uploadedFiles = document.getElementById("js-upload-files");

    formElement.addEventListener("submit", function(e){
	if (e.preventDefault) e.preventDefault();
	var fd = new FormData();

	for (var i = 0; i < uploadedFiles.files.length; i++){
	    fd.append("media",uploadedFiles.files[i]);
	    var request = new XMLHttpRequest();
	    request.open("POST", "/admin/media/create");
	    request.send(fd);

	    request.onreadystatechange = function(){
		if(request.readyState == 4 && request.status == 200){
		    var file = JSON.parse(request.responseText);
		    var element = document.createElement('div');
		    var insert = document.createElement('a');
		    insert.appendChild(document.createTextNode("Insert"));
		    var del = document.createElement('a');
		    del.appendChild(document.createTextNode("Delete"));
		    insert.addEventListener('click', function(){
			switch(file.Type){
			case 'image/png':
			case 'image/jpeg':
			case 'image/gif':
			    insertImage(file);
			    break;
			case 'application/x-download':
			case 'application/x-rar-compressed':
			case 'application/x-zip-compressed':
			    insertDownloadLink(file);
			    break;
			case 'video/webm':
			    insertVideo(file);
			    break;
			}
		    });
		    del.addEventListener('click',function(){
			deleteFile(file);
		    });

		    element.setAttribute('class', 'list-group-item list-group-item-success');
		    element.setAttribute('id', 'file-'+file.Id);
		    insert.setAttribute('class', 'pull-right btn btn-sm btn-primary');
		    del.setAttribute('class', 'pull-right btn btn-sm btn-default');
		    element.appendChild(document.createTextNode(file.Filename));
		    element.appendChild(insert);
		    element.appendChild(del);

		    var uploadedFilesList = document.getElementById('uploadedFiles');
		    uploadedFilesList.insertBefore(element,uploadedFilesList.firstChild);
		}

	    }
	}

	return false;
    });

});

function deleteFile(file){
    var request = new XMLHttpRequest();
    request.open("GET", "/admin/media/delete/" + file.Id);
    request.send();
    request.onreadystatechange = function(){
	if(request.readyState == 4 && request.status == 200){
	    var element = document.getElementById("file-"+file.Id);
	    element.parentNode.removeChild(element);

	}
    }
}

function insertImage(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '![fit-width](/static/uploads/'+file.Filename+' )';
    insertAtCursor(contentArea,text);
}

function insertDownloadLink(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '[ '+file.Filename+'](/static/uploads/'+file.Filename+' )';
    insertAtCursor(contentArea,text);
}
function insertVideo(file){
    var contentArea = document.getElementById("ContentArea");
    var text = '\<video src="/static/uploads/'+file.Filename+'" controls>Your browser does not support the video element.</video>';
    insertAtCursor(contentArea,text);
}

function insertAtCursor(myField, myValue) {
    //IE support
    if (document.selection) {
        myField.focus();
        sel = document.selection.createRange();
        sel.text = myValue;

    }
    //MOZILLA and others
    else if (myField.selectionStart || myField.selectionStart == '0') {
        var startPos = myField.selectionStart;
        var endPos = myField.selectionEnd;
        myField.value = myField.value.substring(0, startPos)
            + myValue
            + myField.value.substring(endPos, myField.value.length);
	myField.selectionStart = startPos + myValue.length;
	myField.selectionEnd = startPos + myValue.length;
    } else {
        myField.value += myValue;
    }
}

<|endoftext|>


<commit_before>var admin = (function () {

var exports = {};

function failed_listing_users(xhr, error) {
    util.destroy_loading_indicator($('#subs_page_loading_indicator'));
    ui.report_error("Error listing users or bots", xhr, $("#administration-status"));
}

function failed_listing_streams(xhr, error) {
    ui.report_error("Error listing streams", xhr, $("#administration-status"));
}

function populate_users (realm_people_data) {
    var users_table = $("#admin_users_table");
    var deactivated_users_table = $("#admin_deactivated_users_table");
    var bots_table = $("#admin_bots_table");
    // Clear table rows, but not the table headers
    users_table.find("tr.user_row").remove();
    deactivated_users_table.find("tr.user_row").remove();
    bots_table.find("tr.user_row").remove();

    var active_users = [];
    var deactivated_users = [];
    var bots = [];
    _.each(realm_people_data.members, function (user) {
        if (user.is_bot) {
            bots.push(user);
        } else if (user.is_active) {
            active_users.push(user);
        } else {
            deactivated_users.push(user);
        }
    });

    active_users = _.sortBy(active_users, 'full_name');
    deactivated_users = _.sortBy(deactivated_users, 'full_name');
    bots = _.sortBy(bots, 'full_name');

    _.each(bots, function (user) {
        bots_table.append(templates.render("admin_user_list", {user: user}));
    });
    _.each(active_users, function (user) {
        users_table.append(templates.render("admin_user_list", {user: user}));
    });
    _.each(deactivated_users, function (user) {
        deactivated_users_table.append(templates.render("admin_user_list", {user: user}));
    });
    util.destroy_loading_indicator($('#admin_page_users_loading_indicator'));
    util.destroy_loading_indicator($('#admin_page_bots_loading_indicator'));
    util.destroy_loading_indicator($('#admin_page_deactivated_users_loading_indicator'));
}

function populate_streams (streams_data) {
    var streams_table = $("#admin_streams_table");
    _.each(streams_data.streams, function (stream) {
        streams_table.append(templates.render("admin_streams_list", {stream: stream}));
    });
    util.destroy_loading_indicator($('#admin_page_streams_loading_indicator'));
}

exports.setup_page = function () {
    // create loading indicators
    util.make_loading_indicator($('#admin_page_users_loading_indicator'));
    util.make_loading_indicator($('#admin_page_bots_loading_indicator'));
    util.make_loading_indicator($('#admin_page_streams_loading_indicator'));
    util.make_loading_indicator($('#admin_page_deactivated_users_loading_indicator'));

    // Populate users and bots tables
    $.ajax({
        type:     'GET',
        url:      '/json/users',
        dataType: 'json',
        timeout:  10*1000,
        success: populate_users,
        error: failed_listing_users
    });

    // Populate streams table
    $.ajax({
        type:     'POST',
        url:      '/json/get_public_streams',
        dataType: 'json',
        timeout:  10*1000,
        success: populate_streams,
        error: failed_listing_streams
    });

    // Setup click handlers
    $(".admin_user_table").on("click", ".deactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        $(".active_user_row").removeClass("active_user_row");

        $(e.target).closest(".user_row").addClass("active_user_row");

        var user_name = $(".active_user_row").find('.user_name').text();
        var email = $(".active_user_row").find('.email').text();

        $("#deactivation_user_modal .email").text(email);
        $("#deactivation_user_modal .user_name").text(user_name);
        $("#deactivation_user_modal").modal("show");
    });

    $(".admin_stream_table").on("click", ".deactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        $(".active_stream_row").removeClass("active_stream_row");

        $(e.target).closest(".stream_row").addClass("active_stream_row");

        var stream_name = $(".active_stream_row").find('.stream_name').text();

        $("#deactivation_stream_modal .stream_name").text(stream_name);
        $("#deactivation_stream_modal").modal("show");
    });

    $(".admin_user_table").on("click", ".reactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        // Go up the tree until we find the user row, then grab the email element
        $(e.target).closest(".user_row").addClass("active_user_row");

        var email = $(".active_user_row").find('.email').text();
        $.ajax({
            type: 'POST',
            url: '/json/users/' + $(".active_user_row").find('.email').text() + "/reactivate",
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_user_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_user_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_user_row");
                var button = $(".active_user_row button");
                button.addClass("btn-danger");
                button.removeClass("btn-warning");
                button.addClass("deactivate");
                button.removeClass("reactivate");
                button.text("Deactivate");
                row.removeClass("inactive_user_row");
            }
        });
    });

    $("#do_deactivate_user_button").click(function (e) {
        if ($("#deactivation_user_modal .email").html() !== $(".active_user_row").find('.email').text()) {
            blueslip.error("User deactivation canceled due to non-matching fields.");
            ui.report_message("Deactivation encountered an error. Please reload and try again.",
               $("#home-error"), 'alert-error');
        }
        $("#deactivation_user_modal").modal("hide");
        $(".active_user_row button").prop("disabled", true).text("Working…");
        $.ajax({
            type: 'DELETE',
            url: '/json/users/' + $(".active_user_row").find('.email').text(),
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_user_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_user_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_user_row");
                var button = $(".active_user_row button");
                button.prop("disabled", false);
                button.addClass("btn-warning");
                button.removeClass("btn-danger");
                button.addClass("reactivate");
                button.removeClass("deactivate");
                button.text("Reactivate");
                row.addClass("inactive_user_row");
            }
        });
    });

    $("#do_deactivate_stream_button").click(function (e) {
        if ($("#deactivation_stream_modal .stream_name").text() !== $(".active_stream_row").find('.stream_name').text()) {
            blueslip.error("Stream deactivation canceled due to non-matching fields.");
            ui.report_message("Deactivation encountered an error. Please reload and try again.",
               $("#home-error"), 'alert-error');
        }
        $("#deactivation_stream_modal").modal("hide");
        $(".active_stream_row button").prop("disabled", true).text("Working…");
        $.ajax({
            type: 'DELETE',
            url: '/json/streams/' + encodeURIComponent($(".active_stream_row").find('.stream_name').text()),
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_stream_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_stream_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_stream_row");
                row.remove();
            }
        });
    });
};

return exports;

}());
<commit_msg>Avoid duplicate streams in the Administration page.<commit_after>var admin = (function () {

var exports = {};

function failed_listing_users(xhr, error) {
    util.destroy_loading_indicator($('#subs_page_loading_indicator'));
    ui.report_error("Error listing users or bots", xhr, $("#administration-status"));
}

function failed_listing_streams(xhr, error) {
    ui.report_error("Error listing streams", xhr, $("#administration-status"));
}

function populate_users (realm_people_data) {
    var users_table = $("#admin_users_table");
    var deactivated_users_table = $("#admin_deactivated_users_table");
    var bots_table = $("#admin_bots_table");
    // Clear table rows, but not the table headers
    users_table.find("tr.user_row").remove();
    deactivated_users_table.find("tr.user_row").remove();
    bots_table.find("tr.user_row").remove();

    var active_users = [];
    var deactivated_users = [];
    var bots = [];
    _.each(realm_people_data.members, function (user) {
        if (user.is_bot) {
            bots.push(user);
        } else if (user.is_active) {
            active_users.push(user);
        } else {
            deactivated_users.push(user);
        }
    });

    active_users = _.sortBy(active_users, 'full_name');
    deactivated_users = _.sortBy(deactivated_users, 'full_name');
    bots = _.sortBy(bots, 'full_name');

    _.each(bots, function (user) {
        bots_table.append(templates.render("admin_user_list", {user: user}));
    });
    _.each(active_users, function (user) {
        users_table.append(templates.render("admin_user_list", {user: user}));
    });
    _.each(deactivated_users, function (user) {
        deactivated_users_table.append(templates.render("admin_user_list", {user: user}));
    });
    util.destroy_loading_indicator($('#admin_page_users_loading_indicator'));
    util.destroy_loading_indicator($('#admin_page_bots_loading_indicator'));
    util.destroy_loading_indicator($('#admin_page_deactivated_users_loading_indicator'));
}

function populate_streams (streams_data) {
    var streams_table = $("#admin_streams_table").expectOne();
    streams_table.find("tr.stream_row").remove();
    _.each(streams_data.streams, function (stream) {
        streams_table.append(templates.render("admin_streams_list", {stream: stream}));
    });
    util.destroy_loading_indicator($('#admin_page_streams_loading_indicator'));
}

exports.setup_page = function () {
    // create loading indicators
    util.make_loading_indicator($('#admin_page_users_loading_indicator'));
    util.make_loading_indicator($('#admin_page_bots_loading_indicator'));
    util.make_loading_indicator($('#admin_page_streams_loading_indicator'));
    util.make_loading_indicator($('#admin_page_deactivated_users_loading_indicator'));

    // Populate users and bots tables
    $.ajax({
        type:     'GET',
        url:      '/json/users',
        dataType: 'json',
        timeout:  10*1000,
        success: populate_users,
        error: failed_listing_users
    });

    // Populate streams table
    $.ajax({
        type:     'POST',
        url:      '/json/get_public_streams',
        dataType: 'json',
        timeout:  10*1000,
        success: populate_streams,
        error: failed_listing_streams
    });

    // Setup click handlers
    $(".admin_user_table").on("click", ".deactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        $(".active_user_row").removeClass("active_user_row");

        $(e.target).closest(".user_row").addClass("active_user_row");

        var user_name = $(".active_user_row").find('.user_name').text();
        var email = $(".active_user_row").find('.email').text();

        $("#deactivation_user_modal .email").text(email);
        $("#deactivation_user_modal .user_name").text(user_name);
        $("#deactivation_user_modal").modal("show");
    });

    $(".admin_stream_table").on("click", ".deactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        $(".active_stream_row").removeClass("active_stream_row");

        $(e.target).closest(".stream_row").addClass("active_stream_row");

        var stream_name = $(".active_stream_row").find('.stream_name').text();

        $("#deactivation_stream_modal .stream_name").text(stream_name);
        $("#deactivation_stream_modal").modal("show");
    });

    $(".admin_user_table").on("click", ".reactivate", function (e) {
        e.preventDefault();
        e.stopPropagation();

        // Go up the tree until we find the user row, then grab the email element
        $(e.target).closest(".user_row").addClass("active_user_row");

        var email = $(".active_user_row").find('.email').text();
        $.ajax({
            type: 'POST',
            url: '/json/users/' + $(".active_user_row").find('.email').text() + "/reactivate",
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_user_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_user_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_user_row");
                var button = $(".active_user_row button");
                button.addClass("btn-danger");
                button.removeClass("btn-warning");
                button.addClass("deactivate");
                button.removeClass("reactivate");
                button.text("Deactivate");
                row.removeClass("inactive_user_row");
            }
        });
    });

    $("#do_deactivate_user_button").click(function (e) {
        if ($("#deactivation_user_modal .email").html() !== $(".active_user_row").find('.email').text()) {
            blueslip.error("User deactivation canceled due to non-matching fields.");
            ui.report_message("Deactivation encountered an error. Please reload and try again.",
               $("#home-error"), 'alert-error');
        }
        $("#deactivation_user_modal").modal("hide");
        $(".active_user_row button").prop("disabled", true).text("Working…");
        $.ajax({
            type: 'DELETE',
            url: '/json/users/' + $(".active_user_row").find('.email').text(),
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_user_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_user_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_user_row");
                var button = $(".active_user_row button");
                button.prop("disabled", false);
                button.addClass("btn-warning");
                button.removeClass("btn-danger");
                button.addClass("reactivate");
                button.removeClass("deactivate");
                button.text("Reactivate");
                row.addClass("inactive_user_row");
            }
        });
    });

    $("#do_deactivate_stream_button").click(function (e) {
        if ($("#deactivation_stream_modal .stream_name").text() !== $(".active_stream_row").find('.stream_name').text()) {
            blueslip.error("Stream deactivation canceled due to non-matching fields.");
            ui.report_message("Deactivation encountered an error. Please reload and try again.",
               $("#home-error"), 'alert-error');
        }
        $("#deactivation_stream_modal").modal("hide");
        $(".active_stream_row button").prop("disabled", true).text("Working…");
        $.ajax({
            type: 'DELETE',
            url: '/json/streams/' + encodeURIComponent($(".active_stream_row").find('.stream_name').text()),
            error: function (xhr, error_type) {
                if (xhr.status.toString().charAt(0) === "4") {
                    $(".active_stream_row button").closest("td").html(
                        $("<p>").addClass("text-error").text($.parseJSON(xhr.responseText).msg)
                    );
                } else {
                     $(".active_stream_row button").text("Failed!");
                }
            },
            success: function () {
                var row = $(".active_stream_row");
                row.remove();
            }
        });
    });
};

return exports;

}());
<|endoftext|>


<commit_before>"use strict";

/*global socket*/

$('document').ready(function() {
	$(window).on('action:ajaxify.end', function(err, data) {
		if (data.url.match(/^topic\//)) {
			addHandlers();
		}
	});

	function addHandlers() {
		$('.toggleQuestionStatus').on('click', toggleQuestionStatus);
		$('.toggleSolved').on('click', toggleSolved);
	}

	function toggleQuestionStatus() {
		var tid = ajaxify.variables.get('topic_id');
		socket.emit('plugins.QandA.toggleQuestionStatus', {tid: tid}, function(err, data) {
			app.alertSuccess(data.isQuestion ? 'Topic has been marked as a question' : 'Topic is now a regular thread');
			ajaxify.refresh();
		});
	}

	function toggleSolved() {
		var tid = ajaxify.variables.get('topic_id');
		socket.emit('plugins.QandA.toggleSolved', {tid: tid}, function(err, data) {
			app.alertSuccess(data.isSolved ? 'Topic has been marked as solved' : 'Topic has been marked as unsolved');
			ajaxify.refresh();
		});
	}
});<commit_msg>add question button to composer<commit_after>"use strict";

/*global socket*/

$('document').ready(function() {
	$(window).on('action:ajaxify.end', function(err, data) {
		if (data.url.match(/^topic\//)) {
			addHandlers();
		}
	});

	$(window).on('action:composer.loaded', function(err, data) {
		var item = $('
 Ask as Question
');
		$('#cmp-uuid-' + data.post_uuid + ' .action-bar .dropdown-menu').append(item);

		item.on('click', function() {
			$(window).one('action:composer.topics.post', function(ev, data) {
				callToggleQuestion(data.data.tid);
			});
		});
	});

	function addHandlers() {
		$('.toggleQuestionStatus').on('click', toggleQuestionStatus);
		$('.toggleSolved').on('click', toggleSolved);
	}

	function toggleQuestionStatus() {
		var tid = ajaxify.variables.get('topic_id');
		callToggleQuestion(tid);
	}

	function callToggleQuestion(tid) {
		socket.emit('plugins.QandA.toggleQuestionStatus', {tid: tid}, function(err, data) {
			app.alertSuccess(data.isQuestion ? 'Topic has been marked as a question' : 'Topic is now a regular thread');
			ajaxify.refresh();
		});
	}

	function toggleSolved() {
		var tid = ajaxify.variables.get('topic_id');
		socket.emit('plugins.QandA.toggleSolved', {tid: tid}, function(err, data) {
			app.alertSuccess(data.isSolved ? 'Topic has been marked as solved' : 'Topic has been marked as unsolved');
			ajaxify.refresh();
		});
	}
});<|endoftext|>


<commit_before>document.addEventListener('DOMContentLoaded', ahoy_yo);

function ahoy_yo() {
    var a = document.querySelectorAll('[data-ahoy]');
    if (a && a.length) {
        for (var i in a) {
            if (a.hasOwnProperty(i) && a[i]) {
                a[i].addEventListener('click', function() {
                    yo(this.getAttribute('data-ahoy'));
                });
            }
        }
    }
    else {
        yo();
    }
}

function yo(sel) {
    var h, w, i, l, s, t = false, p = '';

    var yohoho_loading = document.querySelector('#yohoho-loading');
    if (yohoho_loading) {
        yohoho_loading.parentNode.removeChild(yohoho_loading);
    }
    var yohoho_iframe = document.querySelector('#yohoho-iframe');
    if (yohoho_iframe) {
        yohoho_iframe.parentNode.removeChild(yohoho_iframe);
    }
    var yohoho = document.querySelector('#' + ((sel) ? sel : 'yohoho'));
    if (!yohoho) {
        yohoho = document.querySelector('#yohoho-online');
        if (!yohoho) {
            yohoho = document.querySelector('#yohoho-torrent');
            if (!yohoho) {
                return false;
            }
            else {
                t = true;
            }
        }
    }

    var options = [].slice.call(yohoho.attributes).reduce(function (o, a) {
        return /^data-/.test(a.name) && (o[a.name.substr(5)] = a.value), o;
    }, {});

    if (options.title.indexOf('трейлер')+1 || t) {
        options.player = 'trailer';
    }

    options.player = (options.title.indexOf('трейлер')+1 || t)
        ? 'trailer'
        : (!options.player)
            ? 'moonwalk,hdgo,kodik,allserials,iframe,trailer,torrent'
            : options.player;

    var bg = (options.bg && options.bg.replace(/[^0-9a-z]/ig, ''))
        ? options.bg.replace(/[^0-9a-z]/ig, '')
        : '2A3440';

    var btns = {};
    if (options.button) {
        options.button.split(',').forEach(function (button) {
            var btn = button.split(':');
            if (btn.length === 2 && btn[0] && btn[1]) {
                btns[btn[0].trim().toLowerCase()] = btn[1].trim();
            }
        });
    }

    for (var data in options) {
        if (options.hasOwnProperty(data) && options[data]) {
            p += (p)
                ? '&' + data + '=' + encodeURIComponent(options[data])
                : data + '=' + encodeURIComponent(options[data]);
        }
        else {
            options[data] = '';
        }
    }

    if (!options.kinopoisk && !options.title) {
        return false;
    }

    var head = document.head || document.getElementsByTagName('head')[0];
    var css = '#yohoho{border:0;margin:0;padding:0;overflow:hidden;position:relative}#yohoho-loading{z-index:9999;position:absolute;left:0;top:0;width:100%;height:100%;background:#' + bg + ' url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiIGNsYXNzPSJ1aWwtc3BpcmFsIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgY2xhc3M9ImJrIiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTU0LjUgODkuOWMtOS42IDAtMTguNi0zLjktMjUuNC0xMSAtNi44LTcuMS0xMC41LTE2LjYtMTAuNS0yNi43IDAtOC45IDMuMy0xNy4yIDkuMi0yMy41UzQxLjcgMTkgNTAuMiAxOWM4LjQgMCAxNi40IDMuNCAyMi4zIDkuNyA2IDYuMyA5LjIgMTQuNiA5LjIgMjMuNSAwIDE1LjgtMTIuMiAyOC43LTI3LjMgMjguNyAtMTUgMC0yNy4zLTEyLjktMjcuMy0yOC43IDAtMTMuMyAxMC4zLTI0LjIgMjMtMjQuMnMyMyAxMC44IDIzIDI0LjJjMCAxMC44LTguNCAxOS42LTE4LjcgMTkuNiAtMTAuMyAwLTE4LjctOC44LTE4LjctMTkuNiAwLTguMyA2LjUtMTUuMSAxNC40LTE1LjEgNy45IDAgMTQuNCA2LjggMTQuNCAxNS4xIDAgNS44LTQuNSAxMC42LTEwLjEgMTAuNnMtMTAuMS00LjgtMTAuMS0xMC42YzAtMy40IDIuNi02LjEgNS44LTYuMSAzLjIgMCA1LjggMi43IDUuOCA2LjEgMCAwLjktMC43IDEuNi0xLjUgMS42IC0wLjggMC0xLjUtMC43LTEuNS0xLjYgMC0xLjYtMS4zLTIuOS0yLjgtMi45IC0xLjUgMC0yLjggMS4zLTIuOCAyLjkgMCA0LjEgMy4yIDcuNCA3LjEgNy40czcuMS0zLjMgNy4xLTcuNGMwLTYuNi01LjEtMTItMTEuNC0xMiAtNi4zIDAtMTEuNCA1LjQtMTEuNCAxMiAwIDkuMSA3IDE2LjUgMTUuNyAxNi41IDguNiAwIDE1LjctNy40IDE1LjctMTYuNSAwLTExLjYtOS0yMS0yMC0yMXMtMjAgOS40LTIwIDIxYzAgMTQuMSAxMC45IDI1LjUgMjQuMyAyNS41czI0LjMtMTEuNCAyNC4zLTI1LjVjMC0xNi42LTEyLjgtMzAtMjguNi0zMCAtMTUuOCAwLTI4LjYgMTMuNS0yOC42IDMwIDAgOS4yIDMuNCAxNy45IDkuNiAyNC40IDYuMiA2LjUgMTQuNSAxMC4xIDIzLjIgMTAuMXMxNy0zLjYgMjMuMi0xMC4xYzYuMi02LjUgOS42LTE1LjIgOS42LTI0LjQgMC0xMC40LTMuOS0yMC4yLTEwLjktMjcuNiAtNy03LjQtMTYuMy0xMS40LTI2LjMtMTEuNHMtMTkuMyA0LjEtMjYuMyAxMS40UzEzIDQxLjggMTMgNTIuMmMwIDAuOS0wLjcgMS42LTEuNSAxLjZTMTAgNTMuMSAxMCA1Mi4yYzAtMTEuMyA0LjItMjEuOSAxMS44LTI5LjkgNy42LTggMTcuNy0xMi40IDI4LjQtMTIuNCAxMC43IDAgMjAuOCA0LjQgMjguNCAxMi40IDcuNiA4IDExLjggMTguNiAxMS44IDI5LjkgMCAxMC4xLTMuNyAxOS41LTEwLjUgMjYuN0M3My4xIDg2IDY0LjEgODkuOSA1NC41IDg5Ljl6IiBmaWxsPSIjZmZmIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgZnJvbT0iMCA1MCA1MCIgdG89IjM2MCA1MCA1MCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz48L3BhdGg+PC9zdmc+) 50% 50% no-repeat}#yohoho-buttons{position:absolute;z-index:9999;right:0;top:50px;text-align:left}#yohoho-buttons:hover{right:0!important}#yohoho-buttons div{font-family:Verdana,sans-serif;font-weight:normal;text-shadow:none;line-height:normal;font-size:12px;color:#fff;background:#' + bg + ';border-radius:5px 0 0 5px;padding:5px;margin:5px 0 5px 5px;opacity:.7;}#yohoho-buttons div:hover,#yohoho-buttons div.yohoho-active{color:#fff;background:#' + bg + ';cursor:pointer;opacity:1}';
    s = document.createElement('style');
    s.type = 'text/css';
    if (s.styleSheet) {
        s.styleSheet.cssText = css;
    }
    else {
        s.appendChild(document.createTextNode(css));
    }
    head.appendChild(s);

    l = document.createElement('div');
    l.setAttribute('id', 'yohoho-loading');
    yohoho.innerHTML = '';
    yohoho.appendChild(l);

    i = document.createElement('iframe');
    i.setAttribute('id', 'yohoho-iframe');
    i.setAttribute('frameborder', '0');
    i.setAttribute('allowfullscreen', 'allowfullscreen');
    yohoho.appendChild(i);

    if (parseInt(yohoho.offsetWidth)) {
        w = parseInt(yohoho.offsetWidth);
    }
    else if (parseInt(yohoho.parentNode.offsetWidth)) {
        w = (yohoho.parentNode.offsetWidth);
    }
    else {
        w = 610;
    }

    if (parseInt(yohoho.offsetHeight) && w/3 < parseInt(yohoho.offsetHeight)) {
        h = parseInt(yohoho.offsetHeight);
    }
    else if (parseInt(yohoho.parentNode.offsetHeight) && w/3 < parseInt(yohoho.parentNode.offsetHeight)) {
        h = parseInt(yohoho.parentNode.offsetHeight);
    }
    else {
        h = w/2;
    }

    var style = 'width:' + w + 'px;height:' + h + 'px';
    i.setAttribute('style', style);
    i.setAttribute('width', w);
    i.setAttribute('height', h);
    yohoho.setAttribute('style', style);

    var protocol = window.top.location.protocol || 'http';
    httpGetAsync(protocol + '//4h0y.yohoho.cc', p,
        function (players) {
            var first = true;
            var buttons = document.createElement('div');
            buttons.setAttribute('id', 'yohoho-buttons');
            var keys = options.player.split(',');
            var j = 0;
            for (var i = 0, len = keys.length; i < len; i++) {
                var key = keys[i].toLowerCase().trim();
                if (players.hasOwnProperty(key) && players[key]) {
                    players[key] = (key === 'moonwalk' && options.start_time)
                        ? (players[key].indexOf('?')+1)
                            ? players[key] + '&start_time=' + options.start_time
                            : players[key] + '?start_time=' + options.start_time
                        : players[key];
                    var option = document.createElement('div');
                    option.setAttribute('onclick', 'showPlayer("' + players[key] + '", this)');
                    option.dataset.iframe = players[key];
                    if (btns.hasOwnProperty(key) && btns[key]) {
                        j++;
                        option.innerText = j + '►' + btns[key];
                    }
                    else if (key === 'trailer') {
                        j++;
                        option.innerText = j + '► ТРЕЙЛЕР';
                    }
                    else if (key === 'torrent') {
                        j++;
                        option.innerText = j + '► СКАЧАТЬ';
                    }
                    else {
                        j++;
                        option.innerText = j + '► ' + key.toUpperCase();
                    }
                    if (first) {
                        showPlayer(players[key], option, buttons);
                        first = false;
                    }
                    buttons.appendChild(option);
                }
            }
            if (j < 1) {
                var yohohoLoading = document.querySelector('#yohoho-loading');
                yohohoLoading.style.display = 'none';
            }
            else if (j > 1) {
                document.querySelector('#yohoho').appendChild(buttons);
            }
        });

}

function showPlayer(iframe, element, buttons) {
    var yohohoLoading = document.querySelector('#yohoho-loading');
    yohohoLoading.style.display = 'block';
    setTimeout(function () {
        yohohoLoading.style.display = 'none';
    },2000);
    var yohohoIframe = document.querySelector('#yohoho-iframe');
    yohohoIframe.style.display = 'block';
    yohohoIframe.setAttribute('src', iframe);
    yohohoIframe.setAttribute('class', '');
    if (typeof element.setAttribute === 'function') {
        var yohohoActive = document.querySelectorAll('.yohoho-active');
        if (yohohoActive) {
            for (var i = 0; i < yohohoActive.length; i++) {
                yohohoActive[i].setAttribute('class', '');
            }
        }
        element.setAttribute('class', 'yohoho-active');
    }
    var yohohoButtons = (buttons) ? buttons : document.querySelector('#yohoho-buttons');
    if (yohohoButtons) {
        yohohoButtons.style.right = 0;
        setTimeout(function () {
            var btn = setInterval(function () {
                if (parseInt(yohohoButtons.style.right) > -parseInt(yohohoButtons.offsetWidth)+30)  {
                    yohohoButtons.style.right = (parseInt(yohohoButtons.style.right)-1) + 'px';
                }
                else {
                    clearInterval(btn);
                }
            }, 5);
        }, 5000);
    }
}

function httpGetAsync(url, body, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
            callback(tryParseJSON(xmlHttp.responseText));
        }
    };
    xmlHttp.open('POST', url, true);
    xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xmlHttp.send(body);
}

function tryParseJSON(jsonString) {
    try {
        var o = JSON.parse(jsonString);
        if (o && typeof o === "object") {
            return o;
        }
    }
    catch (e) { }
    return {};
}<commit_msg>data-ahoy trailer<commit_after>document.addEventListener('DOMContentLoaded', ahoy_yo);

function ahoy_yo() {
    var a = document.querySelectorAll('[data-ahoy]');
    if (a && a.length) {
        for (var i in a) {
            if (a.hasOwnProperty(i) && a[i]) {
                a[i].addEventListener('click', function() {
                    yo(this.getAttribute('data-ahoy'));
                });
            }
        }
    }
    else {
        yo();
    }
}

function yo(sel) {
    var h, w, i, l, s, t = false, p = '';

    var yohoho_loading = document.querySelector('#yohoho-loading');
    if (yohoho_loading) {
        yohoho_loading.parentNode.removeChild(yohoho_loading);
    }
    var yohoho_iframe = document.querySelector('#yohoho-iframe');
    if (yohoho_iframe) {
        yohoho_iframe.parentNode.removeChild(yohoho_iframe);
    }
    var yohoho = document.querySelector('#' + ((sel) ? sel : 'yohoho'));
    if (!yohoho) {
        yohoho = document.querySelector('#yohoho-online');
        if (!yohoho) {
            yohoho = document.querySelector('#yohoho-torrent');
            if (!yohoho) {
                return false;
            }
            else {
                t = true;
            }
        }
    }

    var options = [].slice.call(yohoho.attributes).reduce(function (o, a) {
        return /^data-/.test(a.name) && (o[a.name.substr(5)] = a.value), o;
    }, {});

    if (options.title && options.title.indexOf('трейлер')+1 || t) {
        options.player = 'trailer';
    }

    options.player = (options.title.indexOf('трейлер')+1 || t)
        ? 'trailer'
        : (!options.player)
            ? 'moonwalk,hdgo,kodik,allserials,iframe,trailer,torrent'
            : options.player;

    var bg = (options.bg && options.bg.replace(/[^0-9a-z]/ig, ''))
        ? options.bg.replace(/[^0-9a-z]/ig, '')
        : '2A3440';

    var btns = {};
    if (options.button) {
        options.button.split(',').forEach(function (button) {
            var btn = button.split(':');
            if (btn.length === 2 && btn[0] && btn[1]) {
                btns[btn[0].trim().toLowerCase()] = btn[1].trim();
            }
        });
    }

    for (var data in options) {
        if (options.hasOwnProperty(data) && options[data]) {
            p += (p)
                ? '&' + data + '=' + encodeURIComponent(options[data])
                : data + '=' + encodeURIComponent(options[data]);
        }
        else {
            options[data] = '';
        }
    }

    if (!options.kinopoisk && !options.title) {
        return false;
    }

    var head = document.head || document.getElementsByTagName('head')[0];
    var css = '#yohoho{border:0;margin:0;padding:0;overflow:hidden;position:relative}#yohoho-loading{z-index:9999;position:absolute;left:0;top:0;width:100%;height:100%;background:#' + bg + ' url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjEyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiIGNsYXNzPSJ1aWwtc3BpcmFsIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgY2xhc3M9ImJrIiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTU0LjUgODkuOWMtOS42IDAtMTguNi0zLjktMjUuNC0xMSAtNi44LTcuMS0xMC41LTE2LjYtMTAuNS0yNi43IDAtOC45IDMuMy0xNy4yIDkuMi0yMy41UzQxLjcgMTkgNTAuMiAxOWM4LjQgMCAxNi40IDMuNCAyMi4zIDkuNyA2IDYuMyA5LjIgMTQuNiA5LjIgMjMuNSAwIDE1LjgtMTIuMiAyOC43LTI3LjMgMjguNyAtMTUgMC0yNy4zLTEyLjktMjcuMy0yOC43IDAtMTMuMyAxMC4zLTI0LjIgMjMtMjQuMnMyMyAxMC44IDIzIDI0LjJjMCAxMC44LTguNCAxOS42LTE4LjcgMTkuNiAtMTAuMyAwLTE4LjctOC44LTE4LjctMTkuNiAwLTguMyA2LjUtMTUuMSAxNC40LTE1LjEgNy45IDAgMTQuNCA2LjggMTQuNCAxNS4xIDAgNS44LTQuNSAxMC42LTEwLjEgMTAuNnMtMTAuMS00LjgtMTAuMS0xMC42YzAtMy40IDIuNi02LjEgNS44LTYuMSAzLjIgMCA1LjggMi43IDUuOCA2LjEgMCAwLjktMC43IDEuNi0xLjUgMS42IC0wLjggMC0xLjUtMC43LTEuNS0xLjYgMC0xLjYtMS4zLTIuOS0yLjgtMi45IC0xLjUgMC0yLjggMS4zLTIuOCAyLjkgMCA0LjEgMy4yIDcuNCA3LjEgNy40czcuMS0zLjMgNy4xLTcuNGMwLTYuNi01LjEtMTItMTEuNC0xMiAtNi4zIDAtMTEuNCA1LjQtMTEuNCAxMiAwIDkuMSA3IDE2LjUgMTUuNyAxNi41IDguNiAwIDE1LjctNy40IDE1LjctMTYuNSAwLTExLjYtOS0yMS0yMC0yMXMtMjAgOS40LTIwIDIxYzAgMTQuMSAxMC45IDI1LjUgMjQuMyAyNS41czI0LjMtMTEuNCAyNC4zLTI1LjVjMC0xNi42LTEyLjgtMzAtMjguNi0zMCAtMTUuOCAwLTI4LjYgMTMuNS0yOC42IDMwIDAgOS4yIDMuNCAxNy45IDkuNiAyNC40IDYuMiA2LjUgMTQuNSAxMC4xIDIzLjIgMTAuMXMxNy0zLjYgMjMuMi0xMC4xYzYuMi02LjUgOS42LTE1LjIgOS42LTI0LjQgMC0xMC40LTMuOS0yMC4yLTEwLjktMjcuNiAtNy03LjQtMTYuMy0xMS40LTI2LjMtMTEuNHMtMTkuMyA0LjEtMjYuMyAxMS40UzEzIDQxLjggMTMgNTIuMmMwIDAuOS0wLjcgMS42LTEuNSAxLjZTMTAgNTMuMSAxMCA1Mi4yYzAtMTEuMyA0LjItMjEuOSAxMS44LTI5LjkgNy42LTggMTcuNy0xMi40IDI4LjQtMTIuNCAxMC43IDAgMjAuOCA0LjQgMjguNCAxMi40IDcuNiA4IDExLjggMTguNiAxMS44IDI5LjkgMCAxMC4xLTMuNyAxOS41LTEwLjUgMjYuN0M3My4xIDg2IDY0LjEgODkuOSA1NC41IDg5Ljl6IiBmaWxsPSIjZmZmIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgZnJvbT0iMCA1MCA1MCIgdG89IjM2MCA1MCA1MCIgZHVyPSIxcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiLz48L3BhdGg+PC9zdmc+) 50% 50% no-repeat}#yohoho-buttons{position:absolute;z-index:9999;right:0;top:50px;text-align:left}#yohoho-buttons:hover{right:0!important}#yohoho-buttons div{font-family:Verdana,sans-serif;font-weight:normal;text-shadow:none;line-height:normal;font-size:12px;color:#fff;background:#' + bg + ';border-radius:5px 0 0 5px;padding:5px;margin:5px 0 5px 5px;opacity:.7;}#yohoho-buttons div:hover,#yohoho-buttons div.yohoho-active{color:#fff;background:#' + bg + ';cursor:pointer;opacity:1}';
    s = document.createElement('style');
    s.type = 'text/css';
    if (s.styleSheet) {
        s.styleSheet.cssText = css;
    }
    else {
        s.appendChild(document.createTextNode(css));
    }
    head.appendChild(s);

    l = document.createElement('div');
    l.setAttribute('id', 'yohoho-loading');
    yohoho.innerHTML = '';
    yohoho.appendChild(l);

    i = document.createElement('iframe');
    i.setAttribute('id', 'yohoho-iframe');
    i.setAttribute('frameborder', '0');
    i.setAttribute('allowfullscreen', 'allowfullscreen');
    yohoho.appendChild(i);

    if (parseInt(yohoho.offsetWidth)) {
        w = parseInt(yohoho.offsetWidth);
    }
    else if (parseInt(yohoho.parentNode.offsetWidth)) {
        w = (yohoho.parentNode.offsetWidth);
    }
    else {
        w = 610;
    }

    if (parseInt(yohoho.offsetHeight) && w/3 < parseInt(yohoho.offsetHeight)) {
        h = parseInt(yohoho.offsetHeight);
    }
    else if (parseInt(yohoho.parentNode.offsetHeight) && w/3 < parseInt(yohoho.parentNode.offsetHeight)) {
        h = parseInt(yohoho.parentNode.offsetHeight);
    }
    else {
        h = w/2;
    }

    var style = 'width:' + w + 'px;height:' + h + 'px';
    i.setAttribute('style', style);
    i.setAttribute('width', w);
    i.setAttribute('height', h);
    yohoho.setAttribute('style', style);

    var protocol = window.top.location.protocol || 'http';
    httpGetAsync(protocol + '//4h0y.yohoho.cc', p,
        function (players) {
            var first = true;
            var buttons = document.createElement('div');
            buttons.setAttribute('id', 'yohoho-buttons');
            var keys = options.player.split(',');
            var j = 0;
            for (var i = 0, len = keys.length; i < len; i++) {
                var key = keys[i].toLowerCase().trim();
                if (players.hasOwnProperty(key) && players[key]) {
                    players[key] = (key === 'moonwalk' && options.start_time)
                        ? (players[key].indexOf('?')+1)
                            ? players[key] + '&start_time=' + options.start_time
                            : players[key] + '?start_time=' + options.start_time
                        : players[key];
                    var option = document.createElement('div');
                    option.setAttribute('onclick', 'showPlayer("' + players[key] + '", this)');
                    option.dataset.iframe = players[key];
                    if (btns.hasOwnProperty(key) && btns[key]) {
                        j++;
                        option.innerText = j + '►' + btns[key];
                    }
                    else if (key === 'trailer') {
                        j++;
                        option.innerText = j + '► ТРЕЙЛЕР';
                    }
                    else if (key === 'torrent') {
                        j++;
                        option.innerText = j + '► СКАЧАТЬ';
                    }
                    else {
                        j++;
                        option.innerText = j + '► ' + key.toUpperCase();
                    }
                    if (first) {
                        showPlayer(players[key], option, buttons);
                        first = false;
                    }
                    buttons.appendChild(option);
                }
            }
            if (j < 1) {
                var yohohoLoading = document.querySelector('#yohoho-loading');
                yohohoLoading.style.display = 'none';
            }
            else if (j > 1) {
                document.querySelector('#yohoho').appendChild(buttons);
            }
        });

}

function showPlayer(iframe, element, buttons) {
    var yohohoLoading = document.querySelector('#yohoho-loading');
    yohohoLoading.style.display = 'block';
    setTimeout(function () {
        yohohoLoading.style.display = 'none';
    },2000);
    var yohohoIframe = document.querySelector('#yohoho-iframe');
    yohohoIframe.style.display = 'block';
    yohohoIframe.setAttribute('src', iframe);
    yohohoIframe.setAttribute('class', '');
    if (typeof element.setAttribute === 'function') {
        var yohohoActive = document.querySelectorAll('.yohoho-active');
        if (yohohoActive) {
            for (var i = 0; i < yohohoActive.length; i++) {
                yohohoActive[i].setAttribute('class', '');
            }
        }
        element.setAttribute('class', 'yohoho-active');
    }
    var yohohoButtons = (buttons) ? buttons : document.querySelector('#yohoho-buttons');
    if (yohohoButtons) {
        yohohoButtons.style.right = 0;
        setTimeout(function () {
            var btn = setInterval(function () {
                if (parseInt(yohohoButtons.style.right) > -parseInt(yohohoButtons.offsetWidth)+30)  {
                    yohohoButtons.style.right = (parseInt(yohohoButtons.style.right)-1) + 'px';
                }
                else {
                    clearInterval(btn);
                }
            }, 5);
        }, 5000);
    }
}

function httpGetAsync(url, body, callback) {
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.onreadystatechange = function() {
        if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
            callback(tryParseJSON(xmlHttp.responseText));
        }
    };
    xmlHttp.open('POST', url, true);
    xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    xmlHttp.send(body);
}

function tryParseJSON(jsonString) {
    try {
        var o = JSON.parse(jsonString);
        if (o && typeof o === "object") {
            return o;
        }
    }
    catch (e) { }
    return {};
}<|endoftext|>


<commit_before>#!/usr/bin/env node

var Q        = require('q');
var fs       = require('fs');
var open     = require('open');
var events   = require('events');
var https    = require('https');
var libapi   = require('./api');
var libdata  = require('./data');
var termMenu = require('terminal-menu-2');
var exec     = require('child_process').exec;
var spawn    = require('child_process').spawn;

var OPTIONS  = require('./getopts');
var isWin32  = process.platform === 'win32';
var COLUMNS  = process.stdout.columns || 80;
var ROWS     = process.stdout.rows || 24;
var colMax   = COLUMNS - 4;
var rowMax   = ROWS - 2;

var MENU, OFFSET = 0, RUNNING = {}, ITEMS = [], HASH;
var INDICATOR_ON = ' ◉ ', INDICATOR_OFF = ' ◯ ';
var VIDEO_TO_START = 'Play this video', VIDEO_TO_STOP = 'Stop this video';

// the last line of Windows' CMD window always is empty and in input state
if (isWin32) rowMax -= 1;

Q().
then(function() {
  ITEMS = libdata.readCache('subscriptions');
  HASH = libdata.readCache('hash');
  makeMenu();
}).
then(function() {
  return libdata.checkCookie();
}).
then(function(cookie) {
  if (!MENU) process.stdout.write('Retrieving list of videos ... ');
  var pagesNeeded = 10;
  return libapi.subscription.get(serial(pagesNeeded), cookie);
}).
then(function(data) {
  var hash = cacheHash(data);
  libdata.saveCache(data, 'subscriptions');
  libdata.saveCache(hash, 'hash');
  ITEMS = data;
  if (HASH !== hash) {     // if there are new videos, update menu
    if (!MENU || !MENU.detailsPage) { // update menu if we are not on details page
      makeMenu();
    }
  }
}).
catch(function(err) {
  error(err);
});

// events

var ytEvents = new events.EventEmitter();

ytEvents.on('start', function(index) {
  var url = ITEMS[index + OFFSET].url;
  if (MENU.detailsPage) {
    if (MENU.items[0].label === VIDEO_TO_STOP) {
      return killall(RUNNING[url].pid);
    }
    MENU.items[0].label = VIDEO_TO_STOP;
    MENU._drawRow(0);
  } else {
    if (MENU.items[index].label.slice(0, 3) === INDICATOR_ON) {
      return killall(RUNNING[url].pid);
    }
    MENU.items[index].label = INDICATOR_ON + MENU.items[index].label.slice(3);
    MENU._drawRow(index);
  }
  // use --player-no-close to prevent video player exiting too early
  // vlc's --play-and-exit option does not work in Mac OS X
  // so we use verbose stderr to see when the video really ends
  var livestreamer = spawn('livestreamer', [
    '--player-no-close',
    url, OPTIONS.f,
    '--player-args', '--video-on-top {filename} --verbose 2',
    '--verbose-player'
  ]);
  livestreamer.stderr.on('data', function(chunk) {
    if (chunk.toString().indexOf('finished input') > -1) {
      if (!RUNNING[url]) return;
      killall(RUNNING[url].pid);
    }
  });
  livestreamer.on('error', function(e) {
    error('Make sure you have access to command `livestreamer`.');
  });
  livestreamer.on('exit', function() {
    ytEvents.emit('end', url);
  });
  RUNNING[url] = livestreamer;
});

ytEvents.on('end', function(url) {
  var index = -1;
  for (var i = 0; i < ITEMS.length; i++) {
    if (ITEMS[i + OFFSET].url === url) {
      index = i;
      break;
    }
  }
  if (index > -1) {
    if (MENU.detailsPage) {
      MENU.items[0].label = VIDEO_TO_START;
      MENU._drawRow(0);
    } else {
      MENU.items[index].label = INDICATOR_OFF + MENU.items[index].label.slice(3);
      MENU._drawRow(index);
    }
  }
  delete RUNNING[url];
});

function error(err) {
  if (MENU) {
    MENU.reset();
    MENU.close();
  }
  console.error(err.stack ? err.stack : err);
  try {
    process.kill();
  } catch(e) {
    process.exit(1);
  }
}

// serial(2) = [1, 2]  //  serial(5) = [1, 2, 3, 4, 5]
function serial(n) {
  return Array.apply(undefined, { length: n }).map(Function.call, function(i) {
    return i + 1;
  });
}

function killall(pid) {
  if (!pid) return;
  if (isWin32) {
    return exec('taskkill /pid ' + pid + ' /T /F');
  }
  exec('pgrep -P ' + pid, function(error, stdout, stderr) {
    if (stdout) {
      var pids = stdout.trim().split('\n');
      pids.forEach(function(pid) {
        if (pid) process.kill(pid, 'SIGKILL');
      });
    }
    process.kill(pid, 'SIGKILL');
  });
}

function cacheHash(items) {
  var text = items.map(function(item) {
    return item.url;
  }).join('\n');
  var shasum = require('crypto').createHash('sha1');
  shasum.update(text);
  return shasum.digest('hex');
}

// split('一a二b三c四五六七', 3) = ["一a", "二b", "三c", "四", "五", "六", "七"]
function split(str, len) {
  if (len < 1) len = 1;
  var chunks = [];
  while (str) {
    var s = '', c = 0;
    for (var i = 0; str[i] && i < len - c; i++) {
      if (/[^\u0000-\u00ff]/.test(str[i])) c++;
      if (i + 1 <= len - c) s += str[i];
    }
    chunks.push(s);
    str = str.slice(s.length);
  }
  return chunks;
}

function slice(str, len) {
  return str.slice(0, len - (str.match(/[^\u0000-\u00ff]/g) || '').length);
}

function pad(n) {
  return (n < 10 ? '0' : '') + n;
}

function makeMenu() {
  if (Object.prototype.toString.call(ITEMS) !== '[object Array]') return;
  if (ITEMS.length === 0) return;

  var selected = -1;
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.reset();
  MENU.write('');
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    MENU.add(makeMenuItem(ITEMS, i + OFFSET));
  }
  if (selected > -1) MENU.selected = selected;
  MENU.on('select', function (label, index) {
    ytEvents.emit('start', index);
  });
  MENU.createStream().pipe(process.stdout);
}

function updateMenuItems() {
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    MENU.items[i].label = makeMenuItem(ITEMS, i + OFFSET);
    MENU._drawRow(i);
  }
}

function makeMenuItem(items, index) {
  var item = items[index];
  var indLen = 1; // indicator length is 2 in `slice`, so distract 1
  var indicator = INDICATOR_OFF;
  if (RUNNING.hasOwnProperty(item.url)) indicator = INDICATOR_ON;
  var title = item.title;
  var duration = item.duration || '';
  if (duration && duration.length === 4) duration = '0' + duration;
  if (duration) duration = '[' + duration + '] ';
  var padding = Array(colMax).join(' ');
  var text = indicator + pad(index + 1) + '. ' + duration + title + padding;
  return slice(text, colMax + indLen);
}

function makeDetailsPage() {
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  var blankLines = rowMax;
  var ITEM = ITEMS[selected + OFFSET];
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.detailsPage = true;
  MENU.reset();

  MENU.write('Title:\n');
  var title = split(ITEM.title, colMax - 2);
  title.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= title.length + 2;

  var username = ITEM.username;
  if (ITEM.verified) username += ' (verified)'
  MENU.write('By: ' + username + '\n');
  MENU.write('\n');
  blankLines -= 2;

  MENU.write('Description:\n');
  var description = split(ITEM.description || '(none)', colMax - 2);
  description.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= description.length + 2;

  var itemurl = split('URL: ' + ITEM.url, colMax - 2);
  itemurl.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= itemurl.length + 1;

  if (RUNNING.hasOwnProperty(ITEM.url)) {
    MENU.add(VIDEO_TO_STOP);
  } else {
    MENU.add(VIDEO_TO_START);
  }
  MENU.add('Open this video in web browser');
  MENU.add('View the thumbnail of this video');
  MENU.write('\n');
  blankLines -= 4;

  blankLines -= 1;  // last line

  for (var i = 0; i < blankLines; i++) {
    MENU.write('\n');
  }

  var infobar = ITEM.time + ' - ' + (ITEM.views || 'no views');
  if (ITEM.duration) infobar += ' - ' + ITEM.duration;
  MENU.write(infobar + '\n');

  var url = ITEM.url, id = ITEM.id, thumburl = ITEM.thumbnails.maxresdefault;
  MENU.on('select', function (label, index) {
    if (index === 0) {
      var i = 0
      for (; i < ITEMS.length; i++) {
        if (ITEMS[i].url === url) {
          break;
        }
      }
      ytEvents.emit('start', i - OFFSET);
    } else if (index === 1) {
      open(url);
    } else if (index === 2) {
      MENU.items[2].label = 'Downloading thumbnail...';
      MENU._drawRow(2);
      var localfile = libdata.tempFile(id + '.jpg');
      var promise;
      if (fs.existsSync(localfile)) {
        promise = Q(localfile);
      } else {
        promise = download(thumburl, localfile);
      }
      promise.then(function(localfile) {
        open(localfile);
      }).finally(function() {
        MENU.items[2].label = 'View the thumbnail of this video';
        MENU._drawRow(2);
      });
    }
  });
  MENU.createStream().pipe(process.stdout);
}

process.stdin.on('data', function(buf) {
  if (!MENU) return;
  var codes = [].join.call(buf, '.');
  var selected;

  if (MENU.detailsPage) {
    // left / backspace / escape
    if ([ '27.91.68', '127', '27' ].indexOf(codes) > -1) {
      selected = MENU._selected;
      MENU.reset();
      MENU.close();
      makeMenu();
      if (selected > -1) MENU.selected = selected;
    }
    return;
  }

  switch (codes) {
  case '9':                // tab
  case '32':               // space
  case '27.91.67':         // right
    selected = MENU.selected;
    makeDetailsPage();
    MENU._selected = selected;
    break;
  case '27.91.66':         // down
    if (MENU.selected === rowMax - 1) {
      if (ITEMS[rowMax + OFFSET]) {
        OFFSET++;
        updateMenuItems();
      }
      MENU.selected = rowMax - 2;
    }
    break;
  case '27.91.65':         // up
    if (MENU.selected === 0) {
      MENU.selected = 1;
      if (ITEMS[OFFSET - 1]) {
        OFFSET--;
        updateMenuItems();
      }
    }
    break;
  case '27.91.72':         // home
    OFFSET = 0;
    MENU.selected = 0;
    updateMenuItems();
    break;
  case '27.91.70':         // end
    OFFSET = ITEMS.length - rowMax;
    MENU.selected = rowMax - 1;
    updateMenuItems();
    break;
  case '27.91.54.126':     // pagedown
    OFFSET += rowMax;
    if (OFFSET + rowMax > ITEMS.length) {
      OFFSET = ITEMS.length - rowMax;
      MENU.selected = rowMax - 1;
    }
    updateMenuItems();
    break;
  case '27.91.53.126':     // pageup
    OFFSET -= rowMax;
    if (OFFSET < 0) {
      OFFSET = 0;
      MENU.selected = 0;
    }
    updateMenuItems();
    break;
  }
});

function download(url, to) {
  var deferred = Q.defer();
  var req = https.get(url, function(res) {
    res.pipe(fs.createWriteStream(to));
    res.on('end', function() {
      deferred.resolve(to);
    });
  });
  req.on('error', function (err) {
    deferred.reject(err);
  });
  req.end();
  return deferred.promise;
}
<commit_msg>fix keys in windows;<commit_after>#!/usr/bin/env node

var Q        = require('q');
var fs       = require('fs');
var open     = require('open');
var events   = require('events');
var https    = require('https');
var libapi   = require('./api');
var libdata  = require('./data');
var termMenu = require('terminal-menu-2');
var exec     = require('child_process').exec;
var spawn    = require('child_process').spawn;

var OPTIONS  = require('./getopts');
var isWin32  = process.platform === 'win32';
var COLUMNS  = process.stdout.columns || 80;
var ROWS     = process.stdout.rows || 24;
var colMax   = COLUMNS - 4;
var rowMax   = ROWS - 2;

var MENU, OFFSET = 0, RUNNING = {}, ITEMS = [], HASH;
var INDICATOR_ON = ' ◉ ', INDICATOR_OFF = ' ◯ ';
var VIDEO_TO_START = 'Play this video', VIDEO_TO_STOP = 'Stop this video';

// the last line of Windows' CMD window always is empty and in input state
if (isWin32) rowMax -= 1;

Q().
then(function() {
  ITEMS = libdata.readCache('subscriptions');
  HASH = libdata.readCache('hash');
  makeMenu();
}).
then(function() {
  return libdata.checkCookie();
}).
then(function(cookie) {
  if (!MENU) process.stdout.write('Retrieving list of videos ... ');
  var pagesNeeded = 10;
  return libapi.subscription.get(serial(pagesNeeded), cookie);
}).
then(function(data) {
  var hash = cacheHash(data);
  libdata.saveCache(data, 'subscriptions');
  libdata.saveCache(hash, 'hash');
  ITEMS = data;
  if (HASH !== hash) {     // if there are new videos, update menu
    if (!MENU || !MENU.detailsPage) { // update menu if we are not on details page
      makeMenu();
    }
  }
}).
catch(function(err) {
  error(err);
});

// events

var ytEvents = new events.EventEmitter();

ytEvents.on('start', function(index) {
  var url = ITEMS[index + OFFSET].url;
  if (MENU.detailsPage) {
    if (MENU.items[0].label === VIDEO_TO_STOP) {
      return killall(RUNNING[url].pid);
    }
    MENU.items[0].label = VIDEO_TO_STOP;
    MENU._drawRow(0);
  } else {
    if (MENU.items[index].label.slice(0, 3) === INDICATOR_ON) {
      return killall(RUNNING[url].pid);
    }
    MENU.items[index].label = INDICATOR_ON + MENU.items[index].label.slice(3);
    MENU._drawRow(index);
  }
  // use --player-no-close to prevent video player exiting too early
  // vlc's --play-and-exit option does not work in Mac OS X
  // so we use verbose stderr to see when the video really ends
  var livestreamer = spawn('livestreamer', [
    '--player-no-close',
    url, OPTIONS.f,
    '--player-args', '--video-on-top {filename} --verbose 2',
    '--verbose-player'
  ]);
  livestreamer.stderr.on('data', function(chunk) {
    if (chunk.toString().indexOf('finished input') > -1) {
      if (!RUNNING[url]) return;
      killall(RUNNING[url].pid);
    }
  });
  livestreamer.on('error', function(e) {
    error('Make sure you have access to command `livestreamer`.');
  });
  livestreamer.on('exit', function() {
    ytEvents.emit('end', url);
  });
  RUNNING[url] = livestreamer;
});

ytEvents.on('end', function(url) {
  var index = -1;
  for (var i = 0; i < ITEMS.length; i++) {
    if (ITEMS[i + OFFSET].url === url) {
      index = i;
      break;
    }
  }
  if (index > -1) {
    if (MENU.detailsPage) {
      MENU.items[0].label = VIDEO_TO_START;
      MENU._drawRow(0);
    } else {
      MENU.items[index].label = INDICATOR_OFF + MENU.items[index].label.slice(3);
      MENU._drawRow(index);
    }
  }
  delete RUNNING[url];
});

function error(err) {
  if (MENU) {
    MENU.reset();
    MENU.close();
  }
  console.error(err.stack ? err.stack : err);
  try {
    process.kill();
  } catch(e) {
    process.exit(1);
  }
}

// serial(2) = [1, 2]  //  serial(5) = [1, 2, 3, 4, 5]
function serial(n) {
  return Array.apply(undefined, { length: n }).map(Function.call, function(i) {
    return i + 1;
  });
}

function killall(pid) {
  if (!pid) return;
  if (isWin32) {
    return exec('taskkill /pid ' + pid + ' /T /F');
  }
  exec('pgrep -P ' + pid, function(error, stdout, stderr) {
    if (stdout) {
      var pids = stdout.trim().split('\n');
      pids.forEach(function(pid) {
        if (pid) process.kill(pid, 'SIGKILL');
      });
    }
    process.kill(pid, 'SIGKILL');
  });
}

function cacheHash(items) {
  var text = items.map(function(item) {
    return item.url;
  }).join('\n');
  var shasum = require('crypto').createHash('sha1');
  shasum.update(text);
  return shasum.digest('hex');
}

// split('一a二b三c四五六七', 3) = ["一a", "二b", "三c", "四", "五", "六", "七"]
function split(str, len) {
  if (len < 1) len = 1;
  var chunks = [];
  while (str) {
    var s = '', c = 0;
    for (var i = 0; str[i] && i < len - c; i++) {
      if (/[^\u0000-\u00ff]/.test(str[i])) c++;
      if (i + 1 <= len - c) s += str[i];
    }
    chunks.push(s);
    str = str.slice(s.length);
  }
  return chunks;
}

function slice(str, len) {
  return str.slice(0, len - (str.match(/[^\u0000-\u00ff]/g) || '').length);
}

function pad(n) {
  return (n < 10 ? '0' : '') + n;
}

function makeMenu() {
  if (Object.prototype.toString.call(ITEMS) !== '[object Array]') return;
  if (ITEMS.length === 0) return;

  var selected = -1;
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.reset();
  MENU.write('');
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    MENU.add(makeMenuItem(ITEMS, i + OFFSET));
  }
  if (selected > -1) MENU.selected = selected;
  MENU.on('select', function (label, index) {
    ytEvents.emit('start', index);
  });
  MENU.createStream().pipe(process.stdout);
}

function updateMenuItems() {
  for (var i = 0; i < Math.min(rowMax, ITEMS.length); i++) {
    MENU.items[i].label = makeMenuItem(ITEMS, i + OFFSET);
    MENU._drawRow(i);
  }
}

function makeMenuItem(items, index) {
  var item = items[index];
  var indLen = 1; // indicator length is 2 in `slice`, so distract 1
  var indicator = INDICATOR_OFF;
  if (RUNNING.hasOwnProperty(item.url)) indicator = INDICATOR_ON;
  var title = item.title;
  var duration = item.duration || '';
  if (duration && duration.length === 4) duration = '0' + duration;
  if (duration) duration = '[' + duration + '] ';
  var padding = Array(colMax).join(' ');
  var text = indicator + pad(index + 1) + '. ' + duration + title + padding;
  return slice(text, colMax + indLen);
}

function makeDetailsPage() {
  if (MENU) {
    selected = MENU.selected;
    MENU.reset();
    MENU.close();
  }
  var blankLines = rowMax;
  var ITEM = ITEMS[selected + OFFSET];
  MENU = termMenu({ width: colMax, fg: OPTIONS.fg, bg: OPTIONS.bg });
  MENU.detailsPage = true;
  MENU.reset();

  MENU.write('Title:\n');
  var title = split(ITEM.title, colMax - 2);
  title.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= title.length + 2;

  var username = ITEM.username;
  if (ITEM.verified) username += ' (verified)'
  MENU.write('By: ' + username + '\n');
  MENU.write('\n');
  blankLines -= 2;

  MENU.write('Description:\n');
  var description = split(ITEM.description || '(none)', colMax - 2);
  description.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= description.length + 2;

  var itemurl = split('URL: ' + ITEM.url, colMax - 2);
  itemurl.forEach(function(line) {
    MENU.write(line + '\n');
  });
  MENU.write('\n');
  blankLines -= itemurl.length + 1;

  if (RUNNING.hasOwnProperty(ITEM.url)) {
    MENU.add(VIDEO_TO_STOP);
  } else {
    MENU.add(VIDEO_TO_START);
  }
  MENU.add('Open this video in web browser');
  MENU.add('View the thumbnail of this video');
  MENU.write('\n');
  blankLines -= 4;

  blankLines -= 1;  // last line

  for (var i = 0; i < blankLines; i++) {
    MENU.write('\n');
  }

  var infobar = ITEM.time + ' - ' + (ITEM.views || 'no views');
  if (ITEM.duration) infobar += ' - ' + ITEM.duration;
  MENU.write(infobar + '\n');

  var url = ITEM.url, id = ITEM.id, thumburl = ITEM.thumbnails.maxresdefault;
  MENU.on('select', function (label, index) {
    if (index === 0) {
      var i = 0
      for (; i < ITEMS.length; i++) {
        if (ITEMS[i].url === url) {
          break;
        }
      }
      ytEvents.emit('start', i - OFFSET);
    } else if (index === 1) {
      open(url);
    } else if (index === 2) {
      MENU.items[2].label = 'Downloading thumbnail...';
      MENU._drawRow(2);
      var localfile = libdata.tempFile(id + '.jpg');
      var promise;
      if (fs.existsSync(localfile)) {
        promise = Q(localfile);
      } else {
        promise = download(thumburl, localfile);
      }
      promise.then(function(localfile) {
        open(localfile);
      }).finally(function() {
        MENU.items[2].label = 'View the thumbnail of this video';
        MENU._drawRow(2);
      });
    }
  });
  MENU.createStream().pipe(process.stdout);
}

process.stdin.on('data', function(buf) {
  if (!MENU) return;
  var codes = [].join.call(buf, '.');
  var selected;

  if (MENU.detailsPage) {
    // left / backspace / backspace (win) / escape
    if ([ '27.91.68', '127', '8', '27' ].indexOf(codes) > -1) {
      selected = MENU._selected;
      MENU.reset();
      MENU.close();
      makeMenu();
      if (selected > -1) MENU.selected = selected;
    }
    return;
  }

  switch (codes) {
  case '9':                // tab
  case '32':               // space
  case '27.91.67':         // right
    selected = MENU.selected;
    makeDetailsPage();
    MENU._selected = selected;
    break;
  case '27.91.66':         // down
    if (MENU.selected === rowMax - 1) {
      if (ITEMS[rowMax + OFFSET]) {
        OFFSET++;
        updateMenuItems();
      }
      MENU.selected = rowMax - 2;
    }
    break;
  case '27.91.65':         // up
    if (MENU.selected === 0) {
      MENU.selected = 1;
      if (ITEMS[OFFSET - 1]) {
        OFFSET--;
        updateMenuItems();
      }
    }
    break;
  case '27.91.72':         // home
  case '27.91.49.126':     // home (win)
    OFFSET = 0;
    MENU.selected = 0;
    updateMenuItems();
    break;
  case '27.91.70':         // end
  case '27.91.52.126':     // end (win)
    OFFSET = ITEMS.length - rowMax;
    MENU.selected = rowMax - 1;
    updateMenuItems();
    break;
  case '27.91.54.126':     // pagedown
    OFFSET += rowMax;
    if (OFFSET + rowMax > ITEMS.length) {
      OFFSET = ITEMS.length - rowMax;
      MENU.selected = rowMax - 1;
    }
    updateMenuItems();
    break;
  case '27.91.53.126':     // pageup
    OFFSET -= rowMax;
    if (OFFSET < 0) {
      OFFSET = 0;
      MENU.selected = 0;
    }
    updateMenuItems();
    break;
  }
});

function download(url, to) {
  var deferred = Q.defer();
  var req = https.get(url, function(res) {
    res.pipe(fs.createWriteStream(to));
    res.on('end', function() {
      deferred.resolve(to);
    });
  });
  req.on('error', function (err) {
    deferred.reject(err);
  });
  req.end();
  return deferred.promise;
}
<|endoftext|>