<commit_before>(function ($, Typer, window, document) {
    'use strict';

    var ANIMATION_END = 'animationend oanimationend webkitAnimationEnd';
    var TRANSITION_END = 'transitionend otransitionend webkitTransitionEnd';

    function setMenuPosition(thisMenu) {
        var callout = $('.typer-ui-float', thisMenu)[0];
        var nested = !!$(thisMenu).parents('.typer-ui-float')[0];
        var rect = callout.getBoundingClientRect();
        if (rect.bottom > $(window).height()) {
            $(callout).removeClass('float-top float-bottom').addClass('float-top');
        } else if (rect.top < 0) {
            $(callout).removeClass('float-top float-bottom').addClass('float-bottom');
        }
        if (rect.right > $(window).width()) {
            $(callout).removeClass('float-left float-right').addClass('float-left');
        } else if (rect.left < 0) {
            $(callout).removeClass('float-left float-right').addClass('float-right');
        }
    }

    function detachCallout(control) {
        var callout = $('
').append($(control.element).children('.typer-ui-float').addClass('is-' + control.type))[0];
        if (control.ui.typer) {
            control.ui.typer.retainFocus(callout);
        }
        $(control.element).click(function (e) {
            control.ui.show(control, callout, control.element, 'left bottom');
        });
    }

    Typer.ui.themes.material = Typer.ui.theme({
        resources: [
            'https://fonts.googleapis.com/css?family=Roboto:400,500,700',
            'https://fonts.googleapis.com/icon?family=Material+Icons',
            'https://cdn.rawgit.com/misonou/jquery-typer/master/css/jquery.typer.material.css'
        ],
        controlActiveClass: 'active',
        controlDisabledClass: 'disabled',
        controlHiddenClass: 'hidden',
        controlPinActiveClass: 'pin-active',
        controlErrorClass: 'error',
        iconset: 'material',
        label: '

',
        labelText: function (ui, control) {
            if (control.is('textbox')) {
                return '';
            }
            if (!(control.contextualParent || ui).is('callout') && ui.getIcon(control)) {
                if (!control.showButtonLabel || !control.contextualParent.showButtonLabel || !ui.showButtonLabel) {
                    return '';
                }
            }
            return '';
        },
        labelIcon: function (ui, control) {
            if ((control.contextualParent || ui).is('dropdown') || control.is('checkbox') || !ui.getIcon(control)) {
                return '';
            }
            return '';
        },
        buttonset: '

',
        buttonlist: '

',
        form: '

',
        menupane: '

',
        toolbar: '

',
        contextmenu: '

',
        group: '

',
        groupStateChange: function (ui, control) {
            setImmediate(function () {
                $(control.element).toggleClass('sep-before', !!$(control.element).prevAll(':not(.hidden)')[0]);
                $(control.element).toggleClass('sep-after', !!$(control.element).nextAll(':not(.hidden):first:not(.typer-ui-group)')[0]);
            });
        },
        button: '<button x:bind="(title:label)">
</button>',
        buttonExecuteOn: 'click',
        file: '<input type="file"/>
',
        fileInit: function (ui, control) {
            $(control.element).find(':file').change(function (e) {
                control.value = e.target.files;
                ui.execute(control);
                setImmediate(function () {
                    var form = document.createElement('form');
                    form.appendChild(e.target);
                    form.reset();
                    $(e.target).prependTo(control.element);
                });
            });
        },
        callout: '

',
        calloutExecuteOn: 'click',
        calloutInit: function (ui, control) {
            if (!control.contextualParent.is('callout contextmenu')) {
                detachCallout(control);
            }
        },
        dropdown: '<button class="typer-ui-dropdown" x:bind="(title:label)">

</button>',
        dropdownInit: function (ui, control) {
            if (!control.contextualParent.is('callout contextmenu')) {
                detachCallout(control);
            }
        },
        checkbox: '<button class="typer-ui-checkbox" x:bind="(title:label)">
</button>',
        checkboxInit: function (ui, control) {
            $(control.element).click(function () {
                control.value = $(this).toggleClass('checked').hasClass('checked');
                ui.execute(control);
            });
        },
        checkboxStateChange: function (ui, control) {
            $(control.element).toggleClass('checked', !!control.value);
        },
        textboxInner: '

',
        textboxCombo: '

',
        textbox: '

',
        textboxInit: function (ui, control) {
            var editable = $('[contenteditable]', control.element)[0];
            control.preset = Typer.preset(editable, control.preset, $.extend({}, control.presetOptions, {
                focusin: function (e) {
                    $(control.element).closest('.typer-ui-textbox').addClass('focused');
                },
                focusout: function (e) {
                    $(control.element).closest('.typer-ui-textbox').removeClass('focused');
                },
                stateChange: function (e) {
                    var topElement = e.typer.element;
                    var style = window.getComputedStyle(topElement);
                    if (style.whiteSpace === 'nowrap' && style.overflow === 'hidden') {
                        var rect = topElement.getBoundingClientRect();
                        var pos = e.typer.getSelection().extendCaret.getRange().getBoundingClientRect();
                        if (pos.left > rect.right) {
                            topElement.style.textIndent = parseInt(style.textIndent) - (pos.left - rect.right + 5) + 'px';
                        } else if (pos.left < rect.left) {
                            topElement.style.textIndent = Math.min(0, parseInt(style.textIndent) + (rect.left - pos.left + 5)) + 'px';
                        }
                    }
                },
                contentChange: function (e) {
                    control.value = control.preset.getValue();
                    $(control.element).toggleClass('empty', !control.preset.hasContent()).removeClass('error');
                    if (e.typer.focused()) {
                        ui.execute(control);
                    }
                }
            }));
            control.preset.parentControl = control;
            $(control.element).toggleClass('empty', !control.preset.hasContent());
        },
        textboxValidate: function (ui, control, opt) {
            if (!control.preset.validate()) {
                opt.fail();
            }
        },
        textboxStateChange: function (ui, control) {
            control.preset.setValue(control.value || '');
        },
        dialog: '

',
        dialogOpen: function (ui, control) {
            var resolveButton = control.getControl(control.resolveBy);
            var $wrapper = $(ui.element).find('.typer-ui-dialog-wrapper');
            var $content = $(ui.element).find('.typer-ui-dialog-content');

            $(ui.element).appendTo(document.body);
            $wrapper.click(function (e) {
                if (e.target === $wrapper[0]) {
                    $content.addClass('pop');
                }
            });
            $content.bind(ANIMATION_END, function () {
                $content.removeClass('pop');
            });
            if (resolveButton) {
                $(resolveButton.element).addClass('pin-active');
            }
            setImmediate(function () {
                $(control.element).addClass('open');
                if (control.pinnable && ui.parentControl && Typer.ui.matchWSDelim(ui.parentControl.pinDirection, 'left right top bottom')) {
                    $wrapper.addClass('pinned pinned-' + ui.parentControl.pinDirection);
                    $(ui.parentControl.element).addClass('pin-active');
                    Typer.ui.pin(control, $wrapper[0], ui.parentControl.element, ui.parentControl.pinDirection);
                }
            });
        },
        dialogWaiting: function (ui, control) {
            $(control.element).addClass('loading');
        },
        dialogError: function (ui, control) {
            $(control.element).removeClass('loading');
        },
        dialogClose: function (ui, control) {
            $(control.element).addClass('closing').one(TRANSITION_END, function () {
                $(ui.element).remove();
                if (control.pinnable && ui.parentControl) {
                    $(ui.parentControl.element).removeClass('pin-active');
                    Typer.ui.unpin(control);
                }
            });
        },
        afterShow: function (ui, control, data) {
            if (control.is('dialog contextmenu')) {
                $(data.element).removeClass('closing').addClass('open');
            }
        },
        beforeHide: function (ui, control, data) {
            if (control.is('dialog contextmenu')) {
                var deferred = $.Deferred();
                $(data.element).addClass('closing').one(TRANSITION_END, function () {
                    if ($(this).hasClass('closing')) {
                        $(this).removeClass('open closing');
                        deferred.resolve();
                    }
                });
                return deferred.promise();
            }
        },
        positionUpdate: function (ui, control, data) {
            $.each(['left', 'right', 'top', 'bottom'], function (i, v) {
                $(data.element).toggleClass('stick-' + v, !!data.stick[v]);
            });
        },
        controlExecuted: function (ui, control) {
            if (control.is('button') && control.contextualParent.is('callout dropdown contextmenu')) {
                ui.hide(control.contextualParent);
            }
        }
    });

    $(function () {
        var SELECT_EFFECT = '.typer-ui-material button:not(.typer-ui-checkbox), .typer-ui-material .has-clickeffect';
        $(document.body).on('mousedown', SELECT_EFFECT, function (e) {
            var pos = e.currentTarget.getBoundingClientRect();
            var $overlay = $('

').appendTo(e.currentTarget).children().css({
                top: e.clientY - pos.top,
                left: e.clientX - pos.left,
            });
            var p1 = Math.pow(e.clientY - pos.top, 2) + Math.pow(e.clientX - pos.left, 2);
            var p2 = Math.pow(e.clientY - pos.top, 2) + Math.pow(e.clientX - pos.right, 2);
            var p3 = Math.pow(e.clientY - pos.bottom, 2) + Math.pow(e.clientX - pos.left, 2);
            var p4 = Math.pow(e.clientY - pos.bottom, 2) + Math.pow(e.clientX - pos.right, 2);
            var scalePercent = 0.5 + 2 * Math.sqrt(Math.max(p1, p2, p3, p4)) / parseFloat($overlay.css('font-size'));
            setImmediate(function () {
                $overlay.css('transform', $overlay.css('transform') + ' scale(' + scalePercent + ')').addClass('animate-in');
            });
            $overlay.parent().css('border-radius', $(e.currentTarget).css('border-radius'));
        });
        $(document.body).on('mouseup mouseleave', SELECT_EFFECT, function (e) {
            var $overlay = $('.typer-ui-clickeffect', e.currentTarget);
            $overlay.children().addClass('animate-out').bind(TRANSITION_END, function () {
                $overlay.remove();
            });
        });
        $(document.body).on('mouseover', '.typer-ui-callout:has(>.typer-ui-float)', function (e) {
            setMenuPosition(e.currentTarget);
        });
    });

}(jQuery, window.Typer, window, document));
<commit_msg>Theme showCallout<commit_after>(function ($, Typer, window, document) {
    'use strict';

    var ANIMATION_END = 'animationend oanimationend webkitAnimationEnd';
    var TRANSITION_END = 'transitionend otransitionend webkitTransitionEnd';

    function setMenuPosition(thisMenu) {
        var callout = $('.typer-ui-float', thisMenu)[0];
        var nested = !!$(thisMenu).parents('.typer-ui-float')[0];
        var rect = callout.getBoundingClientRect();
        if (rect.bottom > $(window).height()) {
            $(callout).removeClass('float-top float-bottom').addClass('float-top');
        } else if (rect.top < 0) {
            $(callout).removeClass('float-top float-bottom').addClass('float-bottom');
        }
        if (rect.right > $(window).width()) {
            $(callout).removeClass('float-left float-right').addClass('float-left');
        } else if (rect.left < 0) {
            $(callout).removeClass('float-left float-right').addClass('float-right');
        }
    }

    function detachCallout(control) {
        control.callout = $('
').append($(control.element).children('.typer-ui-float').addClass('is-' + control.type))[0];
        if (control.ui.typer) {
            control.ui.typer.retainFocus(control.callout);
        }
    }

    Typer.ui.themes.material = Typer.ui.theme({
        resources: [
            'https://fonts.googleapis.com/css?family=Roboto:400,500,700',
            'https://fonts.googleapis.com/icon?family=Material+Icons',
            'https://cdn.rawgit.com/misonou/jquery-typer/master/css/jquery.typer.material.css'
        ],
        controlActiveClass: 'active',
        controlDisabledClass: 'disabled',
        controlHiddenClass: 'hidden',
        controlPinActiveClass: 'pin-active',
        controlErrorClass: 'error',
        iconset: 'material',
        label: '

',
        labelText: function (ui, control) {
            if (control.is('textbox')) {
                return '';
            }
            if (!(control.contextualParent || ui).is('callout') && ui.getIcon(control)) {
                if (!control.showButtonLabel || !control.contextualParent.showButtonLabel || !ui.showButtonLabel) {
                    return '';
                }
            }
            return '';
        },
        labelIcon: function (ui, control) {
            if ((control.contextualParent || ui).is('dropdown') || control.is('checkbox') || !ui.getIcon(control)) {
                return '';
            }
            return '';
        },
        buttonset: '

',
        buttonlist: '

',
        form: '

',
        menupane: '

',
        toolbar: '

',
        contextmenu: '

',
        group: '

',
        groupStateChange: function (ui, control) {
            setImmediate(function () {
                $(control.element).toggleClass('sep-before', !!$(control.element).prevAll(':not(.hidden)')[0]);
                $(control.element).toggleClass('sep-after', !!$(control.element).nextAll(':not(.hidden):first:not(.typer-ui-group)')[0]);
            });
        },
        button: '<button x:bind="(title:label)">
</button>',
        buttonExecuteOn: 'click',
        file: '<input type="file"/>
',
        fileInit: function (ui, control) {
            $(control.element).find(':file').change(function (e) {
                control.value = e.target.files;
                ui.execute(control);
                setImmediate(function () {
                    var form = document.createElement('form');
                    form.appendChild(e.target);
                    form.reset();
                    $(e.target).prependTo(control.element);
                });
            });
        },
        callout: '

',
        calloutExecuteOn: 'click',
        calloutInit: function (ui, control) {
            if (!control.contextualParent.is('callout contextmenu')) {
                detachCallout(control);
            }
        },
        dropdown: '<button class="typer-ui-dropdown" x:bind="(title:label)">

</button>',
        dropdownInit: function (ui, control) {
            if (!control.contextualParent.is('callout contextmenu')) {
                detachCallout(control);
            }
        },
        checkbox: '<button class="typer-ui-checkbox" x:bind="(title:label)">
</button>',
        checkboxInit: function (ui, control) {
            $(control.element).click(function () {
                control.value = $(this).toggleClass('checked').hasClass('checked');
                ui.execute(control);
            });
        },
        checkboxStateChange: function (ui, control) {
            $(control.element).toggleClass('checked', !!control.value);
        },
        textboxInner: '

',
        textboxCombo: '

',
        textbox: '

',
        textboxInit: function (ui, control) {
            var editable = $('[contenteditable]', control.element)[0];
            control.preset = Typer.preset(editable, control.preset, $.extend({}, control.presetOptions, {
                focusin: function (e) {
                    $(control.element).closest('.typer-ui-textbox').addClass('focused');
                },
                focusout: function (e) {
                    $(control.element).closest('.typer-ui-textbox').removeClass('focused');
                },
                stateChange: function (e) {
                    var topElement = e.typer.element;
                    var style = window.getComputedStyle(topElement);
                    if (style.whiteSpace === 'nowrap' && style.overflow === 'hidden') {
                        var rect = topElement.getBoundingClientRect();
                        var pos = e.typer.getSelection().extendCaret.getRange().getBoundingClientRect();
                        if (pos.left > rect.right) {
                            topElement.style.textIndent = parseInt(style.textIndent) - (pos.left - rect.right + 5) + 'px';
                        } else if (pos.left < rect.left) {
                            topElement.style.textIndent = Math.min(0, parseInt(style.textIndent) + (rect.left - pos.left + 5)) + 'px';
                        }
                    }
                },
                contentChange: function (e) {
                    control.value = control.preset.getValue();
                    $(control.element).toggleClass('empty', !control.preset.hasContent()).removeClass('error');
                    if (e.typer.focused()) {
                        ui.execute(control);
                    }
                }
            }));
            control.preset.parentControl = control;
            control.presetOptions = control.preset.getStaticWidget('__preset__').options;
            $(control.element).toggleClass('empty', !control.preset.hasContent());
        },
        textboxValidate: function (ui, control, opt) {
            if (!control.preset.validate()) {
                opt.fail();
            }
        },
        textboxStateChange: function (ui, control) {
            control.preset.setValue(control.value || '');
        },
        dialog: '

',
        dialogOpen: function (ui, control) {
            var resolveButton = control.getControl(control.resolveBy);
            var $wrapper = $(ui.element).find('.typer-ui-dialog-wrapper');
            var $content = $(ui.element).find('.typer-ui-dialog-content');

            $(ui.element).appendTo(document.body);
            $wrapper.click(function (e) {
                if (e.target === $wrapper[0]) {
                    $content.addClass('pop');
                }
            });
            $content.bind(ANIMATION_END, function () {
                $content.removeClass('pop');
            });
            if (resolveButton) {
                $(resolveButton.element).addClass('pin-active');
            }
            setImmediate(function () {
                $(control.element).addClass('open');
                if (control.pinnable && ui.parentControl && Typer.ui.matchWSDelim(ui.parentControl.pinDirection, 'left right top bottom')) {
                    $wrapper.addClass('pinned pinned-' + ui.parentControl.pinDirection);
                    $(ui.parentControl.element).addClass('pin-active');
                    Typer.ui.pin(control, $wrapper[0], ui.parentControl.element, ui.parentControl.pinDirection);
                }
            });
        },
        dialogWaiting: function (ui, control) {
            $(control.element).addClass('loading');
        },
        dialogError: function (ui, control) {
            $(control.element).removeClass('loading');
        },
        dialogClose: function (ui, control) {
            $(control.element).addClass('closing').one(TRANSITION_END, function () {
                $(ui.element).remove();
                if (control.pinnable && ui.parentControl) {
                    $(ui.parentControl.element).removeClass('pin-active');
                    Typer.ui.unpin(control);
                }
            });
        },
        showCallout: function (ui, control) {
            if (control.callout) {
                ui.show(control, control.callout, control.element, 'left bottom');
            }
        },
        afterShow: function (ui, control, data) {
            if (control.is('dialog contextmenu')) {
                $(data.element).removeClass('closing').addClass('open');
            }
        },
        beforeHide: function (ui, control, data) {
            if (control.is('dialog contextmenu')) {
                var deferred = $.Deferred();
                $(data.element).addClass('closing').one(TRANSITION_END, function () {
                    if ($(this).hasClass('closing')) {
                        $(this).removeClass('open closing');
                        deferred.resolve();
                    }
                });
                return deferred.promise();
            }
        },
        positionUpdate: function (ui, control, data) {
            $.each(['left', 'right', 'top', 'bottom'], function (i, v) {
                $(data.element).toggleClass('stick-' + v, !!data.stick[v]);
            });
        },
        controlExecuted: function (ui, control) {
            if (control.is('button') && control.contextualParent.is('callout dropdown contextmenu')) {
                ui.hide(control.contextualParent);
            }
        }
    });

    $(function () {
        var SELECT_EFFECT = '.typer-ui-material button:not(.typer-ui-checkbox), .typer-ui-material .has-clickeffect';
        $(document.body).on('mousedown', SELECT_EFFECT, function (e) {
            var pos = e.currentTarget.getBoundingClientRect();
            var $overlay = $('

').appendTo(e.currentTarget).children().css({
                top: e.clientY - pos.top,
                left: e.clientX - pos.left,
            });
            var p1 = Math.pow(e.clientY - pos.top, 2) + Math.pow(e.clientX - pos.left, 2);
            var p2 = Math.pow(e.clientY - pos.top, 2) + Math.pow(e.clientX - pos.right, 2);
            var p3 = Math.pow(e.clientY - pos.bottom, 2) + Math.pow(e.clientX - pos.left, 2);
            var p4 = Math.pow(e.clientY - pos.bottom, 2) + Math.pow(e.clientX - pos.right, 2);
            var scalePercent = 0.5 + 2 * Math.sqrt(Math.max(p1, p2, p3, p4)) / parseFloat($overlay.css('font-size'));
            setImmediate(function () {
                $overlay.css('transform', $overlay.css('transform') + ' scale(' + scalePercent + ')').addClass('animate-in');
            });
            $overlay.parent().css('border-radius', $(e.currentTarget).css('border-radius'));
        });
        $(document.body).on('mouseup mouseleave', SELECT_EFFECT, function (e) {
            var $overlay = $('.typer-ui-clickeffect', e.currentTarget);
            $overlay.children().addClass('animate-out').bind(TRANSITION_END, function () {
                $overlay.remove();
            });
        });
        $(document.body).on('mouseover', '.typer-ui-callout:has(>.typer-ui-float)', function (e) {
            setMenuPosition(e.currentTarget);
        });
    });

}(jQuery, window.Typer, window, document));
<|endoftext|>


<commit_before>/**
 * Tool that shows a popup when selected.
 *
 * @abstract
 * @class
 * @extends OO.ui.Tool
 * @mixins OO.ui.PopupElement
 *
 * @constructor
 * @param {OO.ui.ToolGroup} toolGroup
 * @param {Object} [config] Configuration options
 */
OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
	// Allow passing positional parameters inside the config object
	if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
		config = toolGroup;
		toolGroup = config.toolGroup;
	}

	// Parent constructor
	OO.ui.PopupTool.super.call( this, toolGroup, config );

	// Mixin constructors
	OO.ui.PopupElement.call( this, config );

	// Initialization
	this.$element
		.addClass( 'oo-ui-popupTool' )
		.append( this.popup.$element );
};

/* Setup */

OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
OO.mixinClass( OO.ui.PopupTool, OO.ui.PopupElement );

/* Methods */

/**
 * Handle the tool being selected.
 *
 * @inheritdoc
 */
OO.ui.PopupTool.prototype.onSelect = function () {
	if ( !this.isDisabled() ) {
		this.popup.toggle();
	}
	this.setActive( false );
	return false;
};

/**
 * Handle the toolbar state being updated.
 *
 * @inheritdoc
 */
OO.ui.PopupTool.prototype.onUpdateState = function () {
	this.setActive( false );
};
<commit_msg>PopupTool: Add description and example<commit_after>/**
 * Popup tools open a popup window when they are selected from the {@link OO.ui.Toolbar toolbar}. Each popup tool is configured
 * with a static name, title, and icon, as well with as any popup configurations. Unlike other tools, popup tools do not require that developers specify
 * an #onSelect or #onUpdateState method, as these methods have been implemented already.
 *
 *     // Example of a popup tool. When selected, a popup tool displays
 *     // a popup window.
 *     function HelpTool( toolGroup, config ) {
 *        OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
 *            padded: true,
 *            label: 'Help',
 *            head: true
 *        } }, config ) );
 *        this.popup.$body.append( '
I am helpful!
' );
 *     };
 *     OO.inheritClass( HelpTool, OO.ui.PopupTool );
 *     HelpTool.static.name = 'help';
 *     HelpTool.static.icon = 'help';
 *     HelpTool.static.title = 'Help';
 *     toolFactory.register( HelpTool );
 *
 * For an example of a toolbar that contains a popup tool, see {@link OO.ui.Toolbar toolbars}. For more information about
 * toolbars in genreral, please see the [OOjs UI documentation on MediaWiki][1].
 *
 * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Toolbars
 *
 * @abstract
 * @class
 * @extends OO.ui.Tool
 * @mixins OO.ui.PopupElement
 *
 * @constructor
 * @param {OO.ui.ToolGroup} toolGroup
 * @param {Object} [config] Configuration options
 */
OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
	// Allow passing positional parameters inside the config object
	if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
		config = toolGroup;
		toolGroup = config.toolGroup;
	}

	// Parent constructor
	OO.ui.PopupTool.super.call( this, toolGroup, config );

	// Mixin constructors
	OO.ui.PopupElement.call( this, config );

	// Initialization
	this.$element
		.addClass( 'oo-ui-popupTool' )
		.append( this.popup.$element );
};

/* Setup */

OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
OO.mixinClass( OO.ui.PopupTool, OO.ui.PopupElement );

/* Methods */

/**
 * Handle the tool being selected.
 *
 * @inheritdoc
 */
OO.ui.PopupTool.prototype.onSelect = function () {
	if ( !this.isDisabled() ) {
		this.popup.toggle();
	}
	this.setActive( false );
	return false;
};

/**
 * Handle the toolbar state being updated.
 *
 * @inheritdoc
 */
OO.ui.PopupTool.prototype.onUpdateState = function () {
	this.setActive( false );
};
<|endoftext|>


<commit_before>var DISCORD = (function(){
  var getTopMessageViewElement = function(){
    let view = DOM.queryReactClass("messages");

    if (view && view.children.length){
      return view.children[0].getAttribute("class").includes("privateChannelPlaceholder-") ? view.children[1] : view.children[0];
    }

    return null;
  };

  var observerTimer = 0, waitingForCleanup = 0;

  return {
    /*
     * Sets up a callback hook to trigger whenever the list of messages is updated. The callback is given a boolean value that is true if there are more messages to load.
     */
    setupMessageUpdateCallback: function(callback){
      var onTimerFinished = function(){
        let topEle = getTopMessageViewElement();

        if (!topEle){
          restartTimer(500);
        }
        else if (!topEle.getAttribute("class").includes("loadingMore-")){
          let messages = DOM.queryReactClass("messages").children.length;

          if (messages < 100){
            waitingForCleanup = 0;
          }

          if (waitingForCleanup > 0){
            --waitingForCleanup;
            restartTimer(750);
          }
          else{
            if (messages > 300){
              waitingForCleanup = 6;

              DOM.setTimer(() => {
                let view = DOM.queryReactClass("messages");
                view.scrollTop = view.scrollHeight/2;
              }, 1);
            }

            callback(topEle.getAttribute("class").includes("hasMore-"));
            restartTimer(200);
          }
        }
        else{
          restartTimer(25);
        }
      };

      var restartTimer = function(delay){
        observerTimer = DOM.setTimer(onTimerFinished, delay);
      };

      onTimerFinished();
      window.DHT_ON_UNLOAD.push(() => window.clearInterval(observerTimer));
    },

    /*
     * Returns internal React state object of an element.
     */
    getReactProps: function(ele){
      var key = Object.keys(ele || {}).find(key => key.startsWith("__reactInternalInstance"));
      return key ? ele[key].memoizedProps : null;
    },

    /*
     * Returns an object containing the selected server name, selected channel name and ID, and the object type.
     * For types DM and GROUP, the server and channel names are identical.
     * For SERVER type, the channel has to be in view, otherwise Discord unloads it.
     */
    getSelectedChannel: function(){
      try{
        var obj;
        var channelListEle = DOM.queryReactClass("privateChannels");

        if (channelListEle){
          var channel = DOM.queryReactClass("selected", channelListEle);

          if (!channel || !("href" in channel) || !channel.href.includes("/@me/")){
            return null;
          }

          var linkSplit = channel.href.split("/");
          var link = linkSplit[linkSplit.length-1];

          if (!(/^\d+$/.test(link))){
            return null;
          }

          var name;

          for(let ele of channel.querySelectorAll("[class^='name']")){
            let node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);

            if (node){
              name = node.nodeValue;
              break;
            }
          }

          if (!name){
            return null;
          }

          var icon = channel.querySelector("img[class*='avatar']");

          obj = {
            "server": name,
            "channel": name,
            "id": link,
            "type": (icon && icon.src.includes("/channel-icons/")) ? "GROUP" : "DM"
          };
        }
        else{
          channelListEle = document.querySelector("div[class*='sidebar'] > div[class*='container']");

          var channel = channelListEle.querySelector("div[class*='scrollerWrap'] > div[class*='scroller'] [class*='modeSelected']").parentElement;
          var props = DISCORD.getReactProps(channel);

          if (!props){
            return null;
          }

          var channelObj = props.children.props.channel;

          if (!channelObj){
            return null;
          }

          obj = {
            "server": channelListEle.querySelector("header > h1").innerText,
            "channel": channelObj.name,
            "id": channelObj.id,
            "type": "SERVER"
          };
        }

        return obj.channel.length === 0 ? null : obj;
      }catch(e){
        return null;
      }
    },

    /*
     * Returns an array containing currently loaded messages.
     */
    getMessages: function(){
      var props = DISCORD.getReactProps(DOM.queryReactClass("messages"));
      var array = props && props.children.find(ele => ele && ele.length);
      var messages = [];

      if (array){
        if (array.length == 2 && !array.every(ele => ele && ele.length)){
          array = array[1];
        }

        for(let obj of array){
          let nested = obj.props.children;

          if (nested && nested.props && nested.props.messages){
            Array.prototype.push.apply(messages, nested.props.messages);
          }
        }
      }

      return messages;
    },

    /*
     * Returns true if the message view is visible.
     */
    isInMessageView: () => !!DOM.queryReactClass("messages"),

    /*
     * Returns true if there are more messages available or if they're still loading.
     */
    hasMoreMessages: function(){
      let classes = getTopMessageViewElement().getAttribute("class");
      return classes.includes("hasMore-") || classes.includes("loadingMore-");
    },

    /*
     * Forces the message view to load older messages by scrolling all the way up.
     */
    loadOlderMessages: function(){
      let view = DOM.queryReactClass("messages");
      view.scrollTop = view.scrollHeight/2;
      view.scrollTop = 0;
    },

    /*
     * Selects the next text channel and returns true, otherwise returns false if there are no more channels.
     */
    selectNextTextChannel: function(){
      var dms = DOM.queryReactClass("privateChannels");

      if (dms){
        var currentChannel = DOM.queryReactClass("selected", dms);
        var nextChannel = currentChannel && currentChannel.nextElementSibling;

        if (!nextChannel || !nextChannel.getAttribute("class").includes("channel-") || !("href" in nextChannel) || !nextChannel.href.includes("/@me/")){
          return false;
        }
        else{
          nextChannel.click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
      else{
        var channelIconNormal = "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z";
        var channelIconSpecial = "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z";

        var isValidChannelClass = cls => cls.includes("wrapper-") && !cls.includes("clickable-");
        var isValidChannelType = ele => !!ele.querySelector('path[d="' + channelIconNormal + '"]') || !!ele.querySelector('path[d="' + channelIconSpecial + '"]');
        var isValidChannel = ele => ele.childElementCount > 0 && isValidChannelClass(ele.children[0].className) && isValidChannelType(ele);

        var channelListEle = document.querySelector("div[class*='sidebar'] > div[class*='container'] > div[class*='scrollerWrap'] > div[class*='scroller']");

        if (!channelListEle){
          return false;
        }

        var allChannels = Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"), isValidChannel);
        var nextChannel = null;

        for(var index = 0; index < allChannels.length-1; index++){
          if (allChannels[index].children[0].className.includes("modeSelected")){
            nextChannel = allChannels[index+1];
            break;
          }
        }

        if (nextChannel === null){
          return false;
        }
        else{
          nextChannel.children[0].click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
    }
  };
})();
<commit_msg>Fix not finding messages & broken autoscroll after Discord update<commit_after>var DISCORD = (function(){
  var getTopMessageViewElement = function(){
    let view = DOM.queryReactClass("messages");

    if (view && view.children.length){
      let topClass = view.children[0].getAttribute("class");
      return topClass.includes("placeholder") || topClass.includes("privateChannelPlaceholder-") ? view.children[1] : view.children[0];
    }

    return null;
  };

  var observerTimer = 0, waitingForCleanup = 0;

  return {
    /*
     * Sets up a callback hook to trigger whenever the list of messages is updated. The callback is given a boolean value that is true if there are more messages to load.
     */
    setupMessageUpdateCallback: function(callback){
      var onTimerFinished = function(){
        let topEle = getTopMessageViewElement();

        if (!topEle){
          restartTimer(500);
        }
        else if (!topEle.getAttribute("class").includes("loadingMore-")){
          let messages = DOM.queryReactClass("messages").children.length;

          if (messages < 100){
            waitingForCleanup = 0;
          }

          if (waitingForCleanup > 0){
            --waitingForCleanup;
            restartTimer(750);
          }
          else{
            if (messages > 300){
              waitingForCleanup = 6;

              DOM.setTimer(() => {
                let view = DOM.queryReactClass("messages");
                view.scrollTop = view.scrollHeight/2;
              }, 1);
            }

            callback(topEle.getAttribute("class").includes("hasMore-"));
            restartTimer(200);
          }
        }
        else{
          restartTimer(25);
        }
      };

      var restartTimer = function(delay){
        observerTimer = DOM.setTimer(onTimerFinished, delay);
      };

      onTimerFinished();
      window.DHT_ON_UNLOAD.push(() => window.clearInterval(observerTimer));
    },

    /*
     * Returns internal React state object of an element.
     */
    getReactProps: function(ele){
      var key = Object.keys(ele || {}).find(key => key.startsWith("__reactInternalInstance"));
      return key ? ele[key].memoizedProps : null;
    },

    /*
     * Returns an object containing the selected server name, selected channel name and ID, and the object type.
     * For types DM and GROUP, the server and channel names are identical.
     * For SERVER type, the channel has to be in view, otherwise Discord unloads it.
     */
    getSelectedChannel: function(){
      try{
        var obj;
        var channelListEle = DOM.queryReactClass("privateChannels");

        if (channelListEle){
          var channel = DOM.queryReactClass("selected", channelListEle);

          if (!channel || !("href" in channel) || !channel.href.includes("/@me/")){
            return null;
          }

          var linkSplit = channel.href.split("/");
          var link = linkSplit[linkSplit.length-1];

          if (!(/^\d+$/.test(link))){
            return null;
          }

          var name;

          for(let ele of channel.querySelectorAll("[class^='name']")){
            let node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);

            if (node){
              name = node.nodeValue;
              break;
            }
          }

          if (!name){
            return null;
          }

          var icon = channel.querySelector("img[class*='avatar']");

          obj = {
            "server": name,
            "channel": name,
            "id": link,
            "type": (icon && icon.src.includes("/channel-icons/")) ? "GROUP" : "DM"
          };
        }
        else{
          channelListEle = document.querySelector("div[class*='sidebar'] > div[class*='container']");

          var channel = channelListEle.querySelector("div[class*='scrollerWrap'] > div[class*='scroller'] [class*='modeSelected']").parentElement;
          var props = DISCORD.getReactProps(channel);

          if (!props){
            return null;
          }

          var channelObj = props.children.props.channel;

          if (!channelObj){
            return null;
          }

          obj = {
            "server": channelListEle.querySelector("header > h1").innerText,
            "channel": channelObj.name,
            "id": channelObj.id,
            "type": "SERVER"
          };
        }

        return obj.channel.length === 0 ? null : obj;
      }catch(e){
        return null;
      }
    },

    /*
     * Returns an array containing currently loaded messages.
     */
    getMessages: function(){
      var props = DISCORD.getReactProps(DOM.queryReactClass("messages"));
      var array = props && props.children.find(ele => ele && ele.length);
      var messages = [];

      if (array){
        if (array.length == 2 && !array.every(ele => ele && ele.length)){
          array = array[1];
        }

        for(let obj of array){
          if (obj.props && obj.props.message){
            messages.push(obj.props.message);
          }
        }
      }

      return messages;
    },

    /*
     * Returns true if the message view is visible.
     */
    isInMessageView: () => !!DOM.queryReactClass("messages"),

    /*
     * Returns true if there are more messages available or if they're still loading.
     */
    hasMoreMessages: function(){
      let classes = getTopMessageViewElement().getAttribute("class");
      return classes.includes("hasMore-") || classes.includes("loadingMore-");
    },

    /*
     * Forces the message view to load older messages by scrolling all the way up.
     */
    loadOlderMessages: function(){
      let view = DOM.queryReactClass("messages");

      if (view.scrollTop > 0){
        view.scrollTop = view.scrollHeight / 2;
        view.scrollTop = 0;
      }
    },

    /*
     * Selects the next text channel and returns true, otherwise returns false if there are no more channels.
     */
    selectNextTextChannel: function(){
      var dms = DOM.queryReactClass("privateChannels");

      if (dms){
        var currentChannel = DOM.queryReactClass("selected", dms);
        var nextChannel = currentChannel && currentChannel.nextElementSibling;

        if (!nextChannel || !nextChannel.getAttribute("class").includes("channel-") || !("href" in nextChannel) || !nextChannel.href.includes("/@me/")){
          return false;
        }
        else{
          nextChannel.click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
      else{
        var channelIconNormal = "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z";
        var channelIconSpecial = "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z";

        var isValidChannelClass = cls => cls.includes("wrapper-") && !cls.includes("clickable-");
        var isValidChannelType = ele => !!ele.querySelector('path[d="' + channelIconNormal + '"]') || !!ele.querySelector('path[d="' + channelIconSpecial + '"]');
        var isValidChannel = ele => ele.childElementCount > 0 && isValidChannelClass(ele.children[0].className) && isValidChannelType(ele);

        var channelListEle = document.querySelector("div[class*='sidebar'] > div[class*='container'] > div[class*='scrollerWrap'] > div[class*='scroller']");

        if (!channelListEle){
          return false;
        }

        var allChannels = Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"), isValidChannel);
        var nextChannel = null;

        for(var index = 0; index < allChannels.length-1; index++){
          if (allChannels[index].children[0].className.includes("modeSelected")){
            nextChannel = allChannels[index+1];
            break;
          }
        }

        if (nextChannel === null){
          return false;
        }
        else{
          nextChannel.children[0].click();
          nextChannel.scrollIntoView(true);
          return true;
        }
      }
    }
  };
})();
<|endoftext|>


<commit_before>/**
 * generic table
 *
 * this class creates a HTML table based on Table model (see below) and modify it based on model changes
 *
 * usage example:
 *
      var table = new Table({
          model: table
      });

      $('body').append(table.render().el);

  * model should be a collection of Rows

 */

/**
 * represents a table row
 */
cdb.ui.common.Row = Backbone.Model.extend({
});

cdb.ui.common.TableData = Backbone.Collection.extend({
    model: cdb.ui.common.Row,

    /**
     * get value for row index and columnName
     */
    getCell: function(index, columnName) {
      var r = this.at(index);
      if(!r) {
        return null;
      }
      return r.get(columnName);
    },

});

/**
 * contains information about the table, mainly the schema
 */
cdb.ui.common.TableProperties = Backbone.Model.extend({

  columnNames: function() {
    return _.map(this.get('schema'), function(c) {
      return c[0];
    });
  },

  columnName: function(idx) {
    return this.columnNames()[idx];
  }
});

/**
 * renders a table row
 */
cdb.ui.common.RowView = cdb.core.View.extend({
  tagName: 'tr',

  initialize: function() {
    this.model.bind('change', this.render, this);
    this.model.bind('destroy', this.clean, this);
    this.model.bind('remove', this.clean, this);
    this.add_related_model(this.model);
    this.order = this.options.order;
  },

  valueView: function(colName, value) {
    return value;
  },

  render: function() {
    var self = this;
    var tr = this.$el;
    tr.html('');
    var row = this.model;
    tr.attr('id', 'row_' + row.id);

    var tdIndex = 0;
    if(this.options.row_header) {
        var td = $('<td>');
        td.append(self.valueView('', ''));
        td.attr('data-x', tdIndex);
        tdIndex++;
        tr.append(td);
    }

    var attrs = this.order || _.keys(row.attributes);
    _(attrs).each(function(key) {
      var value = row.attributes[key];
      if(value !== undefined) {
        var td = $('<td>');
        td.attr('id', 'cell_' + row.id + '_' + key);
        td.attr('data-x', tdIndex);
        tdIndex++;
        td.append(self.valueView(key, value));
        tr.append(td);
      }
    });
    return this;
  },

  getCell: function(x) {
    var childNo = x;
    if(this.options.row_header) {
      ++x;
    }
    return this.$('td:eq(' + x + ')');
  }

});

/**
 * render a table
 * this widget needs two data sources
 * - the table model which contains information about the table (columns and so on). See TableProperties
 * - the model with the data itself (TableData)
 */
cdb.ui.common.Table = cdb.core.View.extend({

  tagName: 'table',
  rowView: cdb.ui.common.RowView,

  events: {
      'click td': '_cellClick'
  },

  default_options: {
  },

  initialize: function() {
    _.defaults(this.options, this.default_options);
    this.dataModel = this.options.dataModel;
    this.rowViews = [];

    // binding
    this.setDataSource(this.dataModel);
    this.model.bind('change', this.render, this);
    this.model.bind('change:dataSource', this.setDataSource, this);

    // assert the rows are removed when table is removed
    this.bind('clean', this.clear_rows, this);

    // prepare for cleaning
    this.add_related_model(this.dataModel);
    this.add_related_model(this.model);
  },

  headerView: function(column) {
      return column[0];
  },

  setDataSource: function(dm) {
    if(this.dataModel) {
      this.dataModel.unbind(null, null, this);
    }
    this.dataModel = dm;
    this.dataModel.bind('reset', this._renderRows, this);
    this.dataModel.bind('add', this.addRow, this);
  },

  _renderHeader: function() {
    var self = this;
    var thead = $("<thead>");
    var tr = $("<tr>");
    if(this.options.row_header) {
      tr.append($("<th>").append(self.headerView(['', 'header'])));
    }
    _(this.model.get('schema')).each(function(col) {
      tr.append($("<th>").append(self.headerView(col)));
    });
    thead.append(tr);
    return thead;
  },

  /**
   * remove all rows
   */
  clear_rows: function() {
    while(this.rowViews.length) {
      // each element removes itself from rowViews
      this.rowViews[0].clean();
    }
    this.rowViews = [];
  },

  /**
   * add rows
   */
  addRow: function(row, collection, options) {
    var self = this;
    var tr = new self.rowView({
      model: row,
      order: this.model.columnNames()
    });

    tr.bind('clean', function() {
      self.rowViews.splice(_.indexOf(self.rowViews,this), 1);
    });

    tr.render();
    if(options && options.index !== undefined && options.index != self.rowViews.length) {

      tr.$el.insertBefore(self.rowViews[options.index].$el);
      self.rowViews.splice(options.index, 0, tr);
      //tr.$el.attr('data-y', options.index);
      // change others view data-y attribute
      for(var i = options.index; i < self.rowViews.length; ++i) {
        self.rowViews[i].$el.attr('data-y', i);
      }
    } else {
      // at the end
      tr.$el.attr('data-y', self.rowViews.length);
      self.$el.append(tr.el);
      self.rowViews.push(tr);
    }
  },

  /**
   * render only data rows
   */
  _renderRows: function() {
    var self = this;
    this.clear_rows();
    this.dataModel.each(function(row) {
      self.addRow(row);
    });
  },

  /**
   * render table
   */
  render: function() {
    var self = this;

    self.$el.html('');

    // render header
    self.$el.append(self._renderHeader());

    // render data
    self._renderRows();

    return this;

  },

  /**
   * return jquery cell element of cell x,y
   */
  getCell: function(x, y) {
    if(this.options.row_header) {
      ++y;
    }
    return this.rowViews[y].getCell(x);
  },

  _cellClick: function(e) {
    e.preventDefault();
    var cell = $(e.currentTarget || e.target);
    var x = parseInt(cell.attr('data-x'), 10);
    var y = parseInt(cell.parent().attr('data-y'), 10);
    this.trigger('cellClick', e, cell, x, y);
  }

});
<commit_msg>fixed trailing comma<commit_after>/**
 * generic table
 *
 * this class creates a HTML table based on Table model (see below) and modify it based on model changes
 *
 * usage example:
 *
      var table = new Table({
          model: table
      });

      $('body').append(table.render().el);

  * model should be a collection of Rows

 */

/**
 * represents a table row
 */
cdb.ui.common.Row = Backbone.Model.extend({
});

cdb.ui.common.TableData = Backbone.Collection.extend({
    model: cdb.ui.common.Row,

    /**
     * get value for row index and columnName
     */
    getCell: function(index, columnName) {
      var r = this.at(index);
      if(!r) {
        return null;
      }
      return r.get(columnName);
    }

});

/**
 * contains information about the table, mainly the schema
 */
cdb.ui.common.TableProperties = Backbone.Model.extend({

  columnNames: function() {
    return _.map(this.get('schema'), function(c) {
      return c[0];
    });
  },

  columnName: function(idx) {
    return this.columnNames()[idx];
  }
});

/**
 * renders a table row
 */
cdb.ui.common.RowView = cdb.core.View.extend({
  tagName: 'tr',

  initialize: function() {
    this.model.bind('change', this.render, this);
    this.model.bind('destroy', this.clean, this);
    this.model.bind('remove', this.clean, this);
    this.add_related_model(this.model);
    this.order = this.options.order;
  },

  valueView: function(colName, value) {
    return value;
  },

  render: function() {
    var self = this;
    var tr = this.$el;
    tr.html('');
    var row = this.model;
    tr.attr('id', 'row_' + row.id);

    var tdIndex = 0;
    if(this.options.row_header) {
        var td = $('<td>');
        td.append(self.valueView('', ''));
        td.attr('data-x', tdIndex);
        tdIndex++;
        tr.append(td);
    }

    var attrs = this.order || _.keys(row.attributes);
    _(attrs).each(function(key) {
      var value = row.attributes[key];
      if(value !== undefined) {
        var td = $('<td>');
        td.attr('id', 'cell_' + row.id + '_' + key);
        td.attr('data-x', tdIndex);
        tdIndex++;
        td.append(self.valueView(key, value));
        tr.append(td);
      }
    });
    return this;
  },

  getCell: function(x) {
    var childNo = x;
    if(this.options.row_header) {
      ++x;
    }
    return this.$('td:eq(' + x + ')');
  }

});

/**
 * render a table
 * this widget needs two data sources
 * - the table model which contains information about the table (columns and so on). See TableProperties
 * - the model with the data itself (TableData)
 */
cdb.ui.common.Table = cdb.core.View.extend({

  tagName: 'table',
  rowView: cdb.ui.common.RowView,

  events: {
      'click td': '_cellClick'
  },

  default_options: {
  },

  initialize: function() {
    _.defaults(this.options, this.default_options);
    this.dataModel = this.options.dataModel;
    this.rowViews = [];

    // binding
    this.setDataSource(this.dataModel);
    this.model.bind('change', this.render, this);
    this.model.bind('change:dataSource', this.setDataSource, this);

    // assert the rows are removed when table is removed
    this.bind('clean', this.clear_rows, this);

    // prepare for cleaning
    this.add_related_model(this.dataModel);
    this.add_related_model(this.model);
  },

  headerView: function(column) {
      return column[0];
  },

  setDataSource: function(dm) {
    if(this.dataModel) {
      this.dataModel.unbind(null, null, this);
    }
    this.dataModel = dm;
    this.dataModel.bind('reset', this._renderRows, this);
    this.dataModel.bind('add', this.addRow, this);
  },

  _renderHeader: function() {
    var self = this;
    var thead = $("<thead>");
    var tr = $("<tr>");
    if(this.options.row_header) {
      tr.append($("<th>").append(self.headerView(['', 'header'])));
    }
    _(this.model.get('schema')).each(function(col) {
      tr.append($("<th>").append(self.headerView(col)));
    });
    thead.append(tr);
    return thead;
  },

  /**
   * remove all rows
   */
  clear_rows: function() {
    while(this.rowViews.length) {
      // each element removes itself from rowViews
      this.rowViews[0].clean();
    }
    this.rowViews = [];
  },

  /**
   * add rows
   */
  addRow: function(row, collection, options) {
    var self = this;
    var tr = new self.rowView({
      model: row,
      order: this.model.columnNames()
    });

    tr.bind('clean', function() {
      self.rowViews.splice(_.indexOf(self.rowViews,this), 1);
    });

    tr.render();
    if(options && options.index !== undefined && options.index != self.rowViews.length) {

      tr.$el.insertBefore(self.rowViews[options.index].$el);
      self.rowViews.splice(options.index, 0, tr);
      //tr.$el.attr('data-y', options.index);
      // change others view data-y attribute
      for(var i = options.index; i < self.rowViews.length; ++i) {
        self.rowViews[i].$el.attr('data-y', i);
      }
    } else {
      // at the end
      tr.$el.attr('data-y', self.rowViews.length);
      self.$el.append(tr.el);
      self.rowViews.push(tr);
    }
  },

  /**
   * render only data rows
   */
  _renderRows: function() {
    var self = this;
    this.clear_rows();
    this.dataModel.each(function(row) {
      self.addRow(row);
    });
  },

  /**
   * render table
   */
  render: function() {
    var self = this;

    self.$el.html('');

    // render header
    self.$el.append(self._renderHeader());

    // render data
    self._renderRows();

    return this;

  },

  /**
   * return jquery cell element of cell x,y
   */
  getCell: function(x, y) {
    if(this.options.row_header) {
      ++y;
    }
    return this.rowViews[y].getCell(x);
  },

  _cellClick: function(e) {
    e.preventDefault();
    var cell = $(e.currentTarget || e.target);
    var x = parseInt(cell.attr('data-x'), 10);
    var y = parseInt(cell.parent().attr('data-y'), 10);
    this.trigger('cellClick', e, cell, x, y);
  }

});
<|endoftext|>


<commit_before>var fs = require('fs');
var Q = require('q');
var path = require('path');
var shelljs = require('shelljs');
var __ = require('underscore');
var utils = require('./utils');
var cordova = require('cordova');
var hooks = require('./hooks');

// Exports
exports.upgradeProject = upgradeProject;
exports.upgradeProjectIfStale = upgradeProjectIfStale;

// The list of platforms we currently have installed
function getInstalledPlatfroms() {
  return ['ios', 'android'].filter(function(p){
    return fs.existsSync(path.join('platforms', p));
  });
}

// Get the list of supported platforms. Returns a promise.
function getSupportedPlatforms() {
  var plats = [];
  return require('./tools-check')()
  .then(function(toolsCheckResults) {
    // TODO(mmocny): any way to use .raw so as not to also call prepare after each platform add?
    if (toolsCheckResults.hasAndroidPlatform) {
      plats.push('android');
    }
    if (toolsCheckResults.hasXcode ) {
      plats.push('ios');
    }
    return plats;
  });
}

// Get the list of all plugins managed by CCA
function getCcaPlugins() {
  var pluginMaps = require('cca-manifest-logic').pluginMaps;
  // The list of plugins is constructed from all plugin IDs in:
  // pluginMaps.DEFAULT_PLUGINS (Array)
  // pluginMaps.*_MAP (objects)
  var ccaPlugins = __.map(pluginMaps, function(val, key) {
    return /_MAP/.test(key) ? __.values(val) : [];
  });
  ccaPlugins.push(pluginMaps.DEFAULT_PLUGINS);
  ccaPlugins = __.uniq(__.flatten(ccaPlugins));

  return ccaPlugins;
}

// This function is run on almost any cca invocation
// It will run "upgrade" if either:
//  1. The project has not platforms installed
//  2. The file ./platforms/created-with-cca-version contains older version string
//  3. The file ./platforms/created-with-cca-version does not exist
function upgradeProjectIfStale() {
  var packageVersion = require('../package').version;
  var installedPlatfroms = getInstalledPlatfroms();

  if (!installedPlatfroms.length) {
    // No platforms installed yet, (ab)use upgradeProject(skipPrompt=true) to install both.
    // Ideally we would do this in pre-prepare, but cordova doesn't run pre-prepare scripts if there
    // are no target platforms, and its unclear how to make it do so with a difference concept
    // for pre-prepare scripts.
    return exports.upgradeProject(true);
  } else {

    var versionFile = path.join('platforms', 'created-with-cca-version');
    var createdWith;
    if (fs.existsSync(versionFile)) {
      createdWith = fs.readFileSync(versionFile, 'utf-8');
    }
    if (createdWith == packageVersion) {
      return Q();
    } else {
      // The platforms/created-with-cca-version file does not exist or contains older version string. Upgrading.
      console.log('This project was not upgraded to cca v' + packageVersion + ' yet.  Attempting to upgrade now...');
      return exports.upgradeProject();
    }
  }
}

function upgradeProject(skipPrompt) {
  var hadPlatforms = [];

  return Q()
  .then(function(){
    if (skipPrompt) return 'y';
    return utils.waitForKey('Warning: Upgrade will replace all files in platforms and plugins. Continue? [y/N] ');
  })
  .then(function(key) {
    if (key != 'y' && key != 'Y') {
      return Q.reject('Okay, nevermind.');
    }
  })
  .then(function() {
    // Upgrading!

    // We don't want the pre/post prepare hooks to fire during upgrade.
    hooks.unregisterHooks();

    // Remove the old file based pre/post prepare hooks
    // TODO: Remove this later. Last version to use file based hooks was 4.0.0 released in Oct 2014.
    shelljs.rm('-f', path.join('hooks', 'before_prepare', 'cca-pre-prepare.js'));
    shelljs.rm('-f', path.join('hooks', 'after_prepare', 'cca-post-prepare.js'));

    // Remember what platforms we had before deleting them. If we only had one, don't install the other after upgrade.
    hadPlatforms = getInstalledPlatfroms();
    shelljs.rm('-rf', 'platforms');

    shelljs.rm('-f', path.join('plugins', 'android.json'));
    shelljs.rm('-f', path.join('plugins', 'ios.json'));

    var installedPlugins = cordova.cordova_lib.PluginInfo.loadPluginsDir('plugins');
    installedPlugins = __.pluck(installedPlugins, 'id');

    // Only remove the CCA managed plugins
    var allCcaPlugins = getCcaPlugins();
    var pluginsToRemove = __.intersection(allCcaPlugins, installedPlugins);
    if (pluginsToRemove && pluginsToRemove.length) {
      return require('./cordova-commands').runCmd(['plugin', 'rm', pluginsToRemove]);
    }
  })
  .then(function() {
    console.log('## First-time build. Detecting available SDKs:');
    return getSupportedPlatforms();
  })
  .then(function(plats) {
    if (plats.length) {
      // If this is a real upgrade, only install the platforms we had before.
      if (hadPlatforms.length) {
        plats = __.intersection(plats, hadPlatforms);
      }
      return require('./cordova-commands').runCmd(['platform', 'add', plats]);
    }
  })
  .then(function() {
    // Turn the hooks back on and fire prepare. The pre-prepare hook is what
    // installs all the cca plugins back.
    hooks.registerHooks();
    return cordova.raw.prepare();
  })
  .then(require('./write-out-cca-version'));
}
<commit_msg>Fix #453: strip create-with-cca-version file of whitespace<commit_after>var fs = require('fs');
var Q = require('q');
var path = require('path');
var shelljs = require('shelljs');
var __ = require('underscore');
var utils = require('./utils');
var cordova = require('cordova');
var hooks = require('./hooks');

// Exports
exports.upgradeProject = upgradeProject;
exports.upgradeProjectIfStale = upgradeProjectIfStale;

// The list of platforms we currently have installed
function getInstalledPlatfroms() {
  return ['ios', 'android'].filter(function(p){
    return fs.existsSync(path.join('platforms', p));
  });
}

// Get the list of supported platforms. Returns a promise.
function getSupportedPlatforms() {
  var plats = [];
  return require('./tools-check')()
  .then(function(toolsCheckResults) {
    // TODO(mmocny): any way to use .raw so as not to also call prepare after each platform add?
    if (toolsCheckResults.hasAndroidPlatform) {
      plats.push('android');
    }
    if (toolsCheckResults.hasXcode ) {
      plats.push('ios');
    }
    return plats;
  });
}

// Get the list of all plugins managed by CCA
function getCcaPlugins() {
  var pluginMaps = require('cca-manifest-logic').pluginMaps;
  // The list of plugins is constructed from all plugin IDs in:
  // pluginMaps.DEFAULT_PLUGINS (Array)
  // pluginMaps.*_MAP (objects)
  var ccaPlugins = __.map(pluginMaps, function(val, key) {
    return /_MAP/.test(key) ? __.values(val) : [];
  });
  ccaPlugins.push(pluginMaps.DEFAULT_PLUGINS);
  ccaPlugins = __.uniq(__.flatten(ccaPlugins));

  return ccaPlugins;
}

// This function is run on almost any cca invocation
// It will run "upgrade" if either:
//  1. The project has not platforms installed
//  2. The file ./platforms/created-with-cca-version contains older version string
//  3. The file ./platforms/created-with-cca-version does not exist
function upgradeProjectIfStale() {
  var packageVersion = require('../package').version;
  var installedPlatfroms = getInstalledPlatfroms();

  if (!installedPlatfroms.length) {
    // No platforms installed yet, (ab)use upgradeProject(skipPrompt=true) to install both.
    // Ideally we would do this in pre-prepare, but cordova doesn't run pre-prepare scripts if there
    // are no target platforms, and its unclear how to make it do so with a difference concept
    // for pre-prepare scripts.
    return exports.upgradeProject(true);
  } else {

    var versionFile = path.join('platforms', 'created-with-cca-version');
    var createdWith;
    if (fs.existsSync(versionFile)) {
      createdWith = fs.readFileSync(versionFile, 'utf-8').trim();
    }
    if (createdWith == packageVersion) {
      return Q();
    } else {
      // The platforms/created-with-cca-version file does not exist or contains older version string. Upgrading.
      console.log('This project was not upgraded to cca v' + packageVersion + ' yet.  Attempting to upgrade now...');
      return exports.upgradeProject();
    }
  }
}

function upgradeProject(skipPrompt) {
  var hadPlatforms = [];

  return Q()
  .then(function(){
    if (skipPrompt) return 'y';
    return utils.waitForKey('Warning: Upgrade will replace all files in platforms and plugins. Continue? [y/N] ');
  })
  .then(function(key) {
    if (key != 'y' && key != 'Y') {
      return Q.reject('Okay, nevermind.');
    }
  })
  .then(function() {
    // Upgrading!

    // We don't want the pre/post prepare hooks to fire during upgrade.
    hooks.unregisterHooks();

    // Remove the old file based pre/post prepare hooks
    // TODO: Remove this later. Last version to use file based hooks was 4.0.0 released in Oct 2014.
    shelljs.rm('-f', path.join('hooks', 'before_prepare', 'cca-pre-prepare.js'));
    shelljs.rm('-f', path.join('hooks', 'after_prepare', 'cca-post-prepare.js'));

    // Remember what platforms we had before deleting them. If we only had one, don't install the other after upgrade.
    hadPlatforms = getInstalledPlatfroms();
    shelljs.rm('-rf', 'platforms');

    shelljs.rm('-f', path.join('plugins', 'android.json'));
    shelljs.rm('-f', path.join('plugins', 'ios.json'));

    var installedPlugins = cordova.cordova_lib.PluginInfo.loadPluginsDir('plugins');
    installedPlugins = __.pluck(installedPlugins, 'id');

    // Only remove the CCA managed plugins
    var allCcaPlugins = getCcaPlugins();
    var pluginsToRemove = __.intersection(allCcaPlugins, installedPlugins);
    if (pluginsToRemove && pluginsToRemove.length) {
      return require('./cordova-commands').runCmd(['plugin', 'rm', pluginsToRemove]);
    }
  })
  .then(function() {
    console.log('## First-time build. Detecting available SDKs:');
    return getSupportedPlatforms();
  })
  .then(function(plats) {
    if (plats.length) {
      // If this is a real upgrade, only install the platforms we had before.
      if (hadPlatforms.length) {
        plats = __.intersection(plats, hadPlatforms);
      }
      return require('./cordova-commands').runCmd(['platform', 'add', plats]);
    }
  })
  .then(function() {
    // Turn the hooks back on and fire prepare. The pre-prepare hook is what
    // installs all the cca plugins back.
    hooks.registerHooks();
    return cordova.raw.prepare();
  })
  .then(require('./write-out-cca-version'));
}
<|endoftext|>


<commit_before>module.exports = {
	setMessages(messages) {
		this.messages = messages
	},
	getMsg(path) {
		const pathParts = path.split('.');
		let message;

		try {
			message = pathParts.reduce(function (obj, pathPart) {
				return obj[pathPart];
			}, this.messages);
		} finally {
			if (!message) {
				return path + ".undefined";
			}
		}

		return message;
	}
}
<commit_msg>add token replacement functionality to 'getMsg' function<commit_after>module.exports = {
	setMessages(messages) {
		this.messages = messages
	},
	getMsg(path) {
		const pathParts = path ? path.split('.') : [""],
			len = arguments.length,
			re = /\{\d+\}/gm
		let message, i;

		try {
			message = pathParts.reduce(function (obj, pathPart) {
				return obj[pathPart];
			}, this.messages);
		} finally {
			if (message) {
				for (i = 1; i < len; i++){
					message = message.replace(
						new RegExp("\\{" + i + "\\}", "gm"),
						arguments[i]
					);
				}
			} else {
				return path + ".undefined";
			}
		}

		return message;
	}
}
<|endoftext|>


<commit_before>// ;; Persistent data structure representing an ordered mapping from
// strings to values, with some convenient update methods.
class OrderedMap {
  constructor(content) {
    this.content = content
  }

  find(key) {
    for (let i = 0; i < this.content.length; i += 2)
      if (this.content[i] == key) return i
    return -1
  }

  // :: (string) → ?any
  // Retrieve the value stored under `key`, or return undefined when
  // no such key exists.
  get(key) {
    let found = this.find(key)
    return found == -1 ? undefined : this.content[found + 1]
  }

  // :: (string, any, ?string) → OrderedMap
  // Create a new map by replacing the value of `key` with a new
  // value, or adding a binding to the end of the map. If `newKey` is
  // given, the key of the binding will be replaced with that key.
  update(key, value, newKey) {
    let self = newKey && newKey != key ? this.remove(newKey) : this
    let found = self.find(key), content = self.content.slice()
    if (found == -1) {
      content.push(newKey || key, value)
    } else {
      content[found + 1] = value
      if (newKey) content[found] = newKey
    }
    return new OrderedMap(content)
  }

  // :: (string) → OrderedMap
  // Return a map with the given key removed, if it existed.
  remove(key) {
    let found = this.find(key)
    if (found == -1) return this
    let content = this.content.slice()
    content.splice(found, 2)
    return new OrderedMap(content)
  }

  // :: (string, any) → OrderedMap
  // Add a new key to the start of the map.
  addToStart(key, value) {
    return new OrderedMap([key, value].concat(this.remove(key).content))
  }

  // :: (string, any) → OrderedMap
  // Add a new key to the end of the map.
  addToEnd(key, value) {
    let content = this.remove(key).content.slice()
    content.push(key, value)
    return new OrderedMap(content)
  }

  // :: (string, string, any) → OrderedMap
  // Add a key after the given key. If `place` is not found, the new
  // key is added to the end.
  addBefore(place, key, value) {
    let without = this.remove(key), content = without.content.slice()
    let found = without.find(place)
    content.splice(found == -1 ? content.length : found, 0, key, value)
    return new OrderedMap(content)
  }

  // :: ((key: string, value: any))
  // Call the given function for each key/value pair in the map, in
  // order.
  forEach(f) {
    for (let i = 0; i < this.content.length; i += 2)
      f(this.content[i], this.content[i + 1])
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a new map by prepending the keys in this map that don't
  // appear in `map` before the keys in `map`.
  prepend(map) {
    map = OrderedMap.from(map)
    if (!map.size) return this
    return new OrderedMap(map.content.concat(this.subtract(map).content))
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a new map by appending the keys in this map that don't
  // appear in `map` after the keys in `map`.
  append(map) {
    map = OrderedMap.from(map)
    if (!map.size) return this
    console.log("append", map.content)
    return new OrderedMap(this.subtract(map).content.concat(map.content))
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a map containing all the keys in this map that don't
  // appear in `map`.
  subtract(map) {
    let result = this
    OrderedMap.from(map).forEach(key => result = result.remove(key))
    return result
  }

  // :: number
  // The amount of keys in this map.
  get size() {
    return this.content.length >> 1
  }

  // :: (?union<Object, OrderedMap>) → OrderedMap
  // Return a map with the given content. If null, create an empty
  // map. If given an ordered map, return that map itself. If given an
  // object, create a map from the object's properties.
  static from(value) {
    if (value instanceof OrderedMap) return value
    let content = []
    if (value) for (let prop in value) content.push(prop, value[prop])
    return new OrderedMap(content)
  }
}
exports.OrderedMap = OrderedMap
<commit_msg>Remove debug statement<commit_after>// ;; Persistent data structure representing an ordered mapping from
// strings to values, with some convenient update methods.
class OrderedMap {
  constructor(content) {
    this.content = content
  }

  find(key) {
    for (let i = 0; i < this.content.length; i += 2)
      if (this.content[i] == key) return i
    return -1
  }

  // :: (string) → ?any
  // Retrieve the value stored under `key`, or return undefined when
  // no such key exists.
  get(key) {
    let found = this.find(key)
    return found == -1 ? undefined : this.content[found + 1]
  }

  // :: (string, any, ?string) → OrderedMap
  // Create a new map by replacing the value of `key` with a new
  // value, or adding a binding to the end of the map. If `newKey` is
  // given, the key of the binding will be replaced with that key.
  update(key, value, newKey) {
    let self = newKey && newKey != key ? this.remove(newKey) : this
    let found = self.find(key), content = self.content.slice()
    if (found == -1) {
      content.push(newKey || key, value)
    } else {
      content[found + 1] = value
      if (newKey) content[found] = newKey
    }
    return new OrderedMap(content)
  }

  // :: (string) → OrderedMap
  // Return a map with the given key removed, if it existed.
  remove(key) {
    let found = this.find(key)
    if (found == -1) return this
    let content = this.content.slice()
    content.splice(found, 2)
    return new OrderedMap(content)
  }

  // :: (string, any) → OrderedMap
  // Add a new key to the start of the map.
  addToStart(key, value) {
    return new OrderedMap([key, value].concat(this.remove(key).content))
  }

  // :: (string, any) → OrderedMap
  // Add a new key to the end of the map.
  addToEnd(key, value) {
    let content = this.remove(key).content.slice()
    content.push(key, value)
    return new OrderedMap(content)
  }

  // :: (string, string, any) → OrderedMap
  // Add a key after the given key. If `place` is not found, the new
  // key is added to the end.
  addBefore(place, key, value) {
    let without = this.remove(key), content = without.content.slice()
    let found = without.find(place)
    content.splice(found == -1 ? content.length : found, 0, key, value)
    return new OrderedMap(content)
  }

  // :: ((key: string, value: any))
  // Call the given function for each key/value pair in the map, in
  // order.
  forEach(f) {
    for (let i = 0; i < this.content.length; i += 2)
      f(this.content[i], this.content[i + 1])
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a new map by prepending the keys in this map that don't
  // appear in `map` before the keys in `map`.
  prepend(map) {
    map = OrderedMap.from(map)
    if (!map.size) return this
    return new OrderedMap(map.content.concat(this.subtract(map).content))
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a new map by appending the keys in this map that don't
  // appear in `map` after the keys in `map`.
  append(map) {
    map = OrderedMap.from(map)
    if (!map.size) return this
    return new OrderedMap(this.subtract(map).content.concat(map.content))
  }

  // :: (union<Object, OrderedMap>) → OrderedMap
  // Create a map containing all the keys in this map that don't
  // appear in `map`.
  subtract(map) {
    let result = this
    OrderedMap.from(map).forEach(key => result = result.remove(key))
    return result
  }

  // :: number
  // The amount of keys in this map.
  get size() {
    return this.content.length >> 1
  }

  // :: (?union<Object, OrderedMap>) → OrderedMap
  // Return a map with the given content. If null, create an empty
  // map. If given an ordered map, return that map itself. If given an
  // object, create a map from the object's properties.
  static from(value) {
    if (value instanceof OrderedMap) return value
    let content = []
    if (value) for (let prop in value) content.push(prop, value[prop])
    return new OrderedMap(content)
  }
}
exports.OrderedMap = OrderedMap
<|endoftext|>


<commit_before>import isFunction from 'lodash/isFunction';
import isSymbol from 'lodash/isSymbol';

const toString = Object.prototype.toString;
const toISOString = Date.prototype.toISOString;
const errorToString = Error.prototype.toString;
const regExpToString = RegExp.prototype.toString;
const symbolToString = Symbol.prototype.toString;

const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;

function printNumber(val) {
  if (val != +val) return 'NaN';
  const isNegativeZero = val === 0 && 1 / val < 0;
  return isNegativeZero ? '-0' : '' + val;
}

function printFunction(val) {
  return '[Function ' + (val.name || 'anonymous') + ']';
}

function printSymbol(val: Symbol): string {
  return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
}

function printError(val: Error): string {
  return '[' + errorToString.call(val) + ']';
}

function printSimpleValue(val, quoteStrings = false) {
  if (val === true || val === false) return '' + val;
  if (val === undefined) return 'undefined';
  if (val === null) return 'null';

  const typeOf = typeof val;

  if (typeOf === 'number') return printNumber(val);
  if (typeOf === 'string') return quoteStrings ? `"${val}"` : val;
  if (isFunction(val)) return printFunction(val);
  if (isSymbol(val)) return printSymbol(val);

  const tag = toString.call(val);
  if (tag === '[object Date]') return isNaN(val.getTime()) ? String(val) : toISOString.call(val);
  if (tag === '[object Error]' || val instanceof Error) return printError(val);
  if (tag === '[object RegExp]') return regExpToString.call(val);

  return null;
}

export default function printValue(value, quoteStrings) {
  let result = printSimpleValue(value, quoteStrings);
  if (result !== null) return result;

  return JSON.stringify(value, function (key, value) {
    let result = printSimpleValue(this[key], quoteStrings);
    if (result !== null) return result
    return value
  }, 2)
}
<commit_msg>Check for Symbol<commit_after>import isFunction from 'lodash/isFunction';
import isSymbol from 'lodash/isSymbol';

const toString = Object.prototype.toString;
const toISOString = Date.prototype.toISOString;
const errorToString = Error.prototype.toString;
const regExpToString = RegExp.prototype.toString;
const symbolToString = typeof Symbol !== 'undefined' ?
  Symbol.prototype.toString :
  ()=> '';

const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;

function printNumber(val) {
  if (val != +val) return 'NaN';
  const isNegativeZero = val === 0 && 1 / val < 0;
  return isNegativeZero ? '-0' : '' + val;
}

function printFunction(val) {
  return '[Function ' + (val.name || 'anonymous') + ']';
}

function printSymbol(val: Symbol): string {
  return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
}

function printError(val: Error): string {
  return '[' + errorToString.call(val) + ']';
}

function printSimpleValue(val, quoteStrings = false) {
  if (val === true || val === false) return '' + val;
  if (val === undefined) return 'undefined';
  if (val === null) return 'null';

  const typeOf = typeof val;

  if (typeOf === 'number') return printNumber(val);
  if (typeOf === 'string') return quoteStrings ? `"${val}"` : val;
  if (isFunction(val)) return printFunction(val);
  if (isSymbol(val)) return printSymbol(val);

  const tag = toString.call(val);
  if (tag === '[object Date]') return isNaN(val.getTime()) ? String(val) : toISOString.call(val);
  if (tag === '[object Error]' || val instanceof Error) return printError(val);
  if (tag === '[object RegExp]') return regExpToString.call(val);

  return null;
}

export default function printValue(value, quoteStrings) {
  let result = printSimpleValue(value, quoteStrings);
  if (result !== null) return result;

  return JSON.stringify(value, function (key, value) {
    let result = printSimpleValue(this[key], quoteStrings);
    if (result !== null) return result
    return value
  }, 2)
}
<|endoftext|>


<commit_before>import React from 'react';

import Promise from 'promise';
import Diff from './Diff';

class DiffPanel extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      diffsArr: [],
      checked: [],
    };
  }

  formatDiffs = (props) => {

    if(props.diffs) {

      let diffsArr = [];
      let checked = [];
      let patches;

      let promises1 = [];
      props.diffs.forEach( (diff, index) => {
          // push all promises to get the patches from every diff
          promises1.push( diff.patches().then((retPatches) => {
            patches = retPatches;
          }) );
        });

      // perform operation on those patches after you get them
      return Promise.all(promises1)
      .then( () => {
        if(patches) {

          let promises2 = [];
          patches.forEach( (patch, fileIndex) => {

            // this array stores the Git line objects, header and file path
            // of a single diff
            let diffs = [];
            let checklets = [];

            // obtain promises for all the hunks belonging to a single
            // patch. Each patch corresponds to a single file
            promises2.push( patch.hunks().then( (hunks) => {

              let promises3 = [];
              hunks.forEach( (hunk, hunkIndex) => {

                // obtain promises for getting the lines from each hunk
                promises3.push(
                  hunk.lines().then( (lines) => {

                    // we don't want the last line if there was no newline in the org file
                    if(lines[lines.length - 1].content().match(/\ No newline at end of file/))
                      lines.pop();

                    // this makes it easier to stage hunks
                    diffs.push({
                      header: hunk.header(),
                      lines: lines,
                      path: patch.oldFile().path()
                    });

                    checklets.push(lines.map((line) => {return false;}));
                  }) );
              });

              // return promises for inserting the lines into diffLines
              return Promise.all(promises3);
            })
            .then( () => {
              checked.push(checklets);
              diffsArr.push({
                diffs: diffs,
                // diff already contains a path, but we need the old path as well as
                // the new one
                path: {
                  old: patch.oldFile().path(),
                  new: patch.newFile().path()
                },
                isModified: patch.isModified(),
              });
            }));
          });

          // return promises for inserting ALL the diffLines belonging to ALL the patches
          return Promise.all(promises2);
        }
      })
      .done( () => {

        /***
         * diffsArr: Array of diffFiles
         * diffFile: All the hunks from one single file
         *  |
         *  |-diffs: Array of diffs of ONE file
         *  |  diff: data of ONE hunk
         *  |   |
         *  |   |- header: string of header
         *  |   |- lines: array of Git line objects
         *  |   |- path: path of hunk
         *  |
         *  |-path: contains path of new and old file
         ***/

        this.setState({
          diffsArr: diffsArr,
          checked: checked
        });
      });
    }
  }

  callbackLine = (isChecked, fileIndex, diffIndex, lineIndex) => {
    this.setState((prevState) => {
      prevState.checked[fileIndex][diffIndex][lineIndex] = isChecked;
      return {
        checked: prevState.checked
      };
    })
  }

  callbackHunk = (isChecked, fileIndex, diffIndex) => {
    this.setState( (prevState) => {
      let checked = prevState.checked;
      checked[fileIndex][diffIndex].forEach( (line, lineIndex) => {
        checked[fileIndex][diffIndex][lineIndex] = isChecked;
      });
      return {
        checked: checked
      };
    })
  }

  componentDidMount = () => {
    this.formatDiffs(this.props);
  }

  componentWillReceiveProps = (newprops) => {
    this.formatDiffs(newprops);
  }

  render = () => {

    const diffStyle = this.props.diffStyle;

    let diffTree = [];
    let diffSelect = [];

    this.state.diffsArr.forEach((diffFile, fileIndex) => {
      let { diffs, path , isModified} = diffFile;

      let formattedLines = [];

      diffs.forEach( (diff, diffIndex) => {
        let { header, lines, path } = diff;

        formattedLines.push(header);
        lines.forEach( (line) => {
            formattedLines.push(String.fromCharCode(line.origin())+line.content());
        });
      });

      // collecting all the header and lines of the hunks in a single file
      // and putting them in a diffDisplay object
      const styles={
        span: {
          fontWeight: 600,
          color: diffStyle.type.color,
        },
        file: {
          color: diffStyle.file.color,
        }
      }
      let diffDisplay = {
        lines:formattedLines,
        path:

                 old
                {path.old}
                 new
                {path.new}

,
      };
      if(isModified) {
        diffTree.unshift(<Diff diff={diffDisplay} key={fileIndex} diffStyle={diffStyle} />);

        // creating a column of checboxes to select individual lines/hunks
        diffSelect.unshift(

        // each div contains all the checkboxes of a single file

          {diffs.map( (diff, diffIndex) => {
            let { header, lines, path } = diff;

            const styles = {
              checkbox: {
                height: 13,
                padding: 0,
                margin: 0,
                marginTop: 1,
                marginBottom: 1,
              }
            };
            // the header calls the callback with an array containing all the difflines
            // and the line calls the callback with its corresponding diffline
            return (

              <input type='checkbox' style={styles.checkbox}
              onChange={(e) => {this.callbackHunk(e.target.checked,fileIndex, diffIndex);}} />
              {lines.map( (line, lineIndex) => {
                return <input type='checkbox' style={styles.checkbox}
                key={lineIndex}
                checked={this.state.checked[fileIndex][diffIndex][lineIndex]}
                onChange={(e) => {this.callbackLine(e.target.checked, fileIndex, diffIndex, lineIndex);}} />;
              })}

            );

          })}

        );
      }
      else {
        diffTree.push(<Diff diff={diffDisplay} key={fileIndex} diffStyle={diffStyle} />);
      }

    });

    const styles = {
      main: {
        display: 'flex',
        width: '100%',
      },
      diffTree: {
        width: '100%',
        border: '1px solid black',
      }
    };
    return(

        {this.props.showSelect?

{diffSelect}
:
        null}

{diffTree}

    );
  }
}

export default DiffPanel;<commit_msg>View: Added flexbox support to div enclosing line select checkboxes<commit_after>import React from 'react';

import Promise from 'promise';
import Diff from './Diff';

class DiffPanel extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      diffsArr: [],
      checked: [],
    };
  }

  formatDiffs = (props) => {

    if(props.diffs) {

      let diffsArr = [];
      let checked = [];
      let patches;

      let promises1 = [];
      props.diffs.forEach( (diff, index) => {
          // push all promises to get the patches from every diff
          promises1.push( diff.patches().then((retPatches) => {
            patches = retPatches;
          }) );
        });

      // perform operation on those patches after you get them
      return Promise.all(promises1)
      .then( () => {
        if(patches) {

          let promises2 = [];
          patches.forEach( (patch, fileIndex) => {

            // this array stores the Git line objects, header and file path
            // of a single diff
            let diffs = [];
            let checklets = [];

            // obtain promises for all the hunks belonging to a single
            // patch. Each patch corresponds to a single file
            promises2.push( patch.hunks().then( (hunks) => {

              let promises3 = [];
              hunks.forEach( (hunk, hunkIndex) => {

                // obtain promises for getting the lines from each hunk
                promises3.push(
                  hunk.lines().then( (lines) => {

                    // we don't want the last line if there was no newline in the org file
                    if(lines[lines.length - 1].content().match(/\ No newline at end of file/))
                      lines.pop();

                    // this makes it easier to stage hunks
                    diffs.push({
                      header: hunk.header(),
                      lines: lines,
                      path: patch.oldFile().path()
                    });

                    checklets.push(lines.map((line) => {return false;}));
                  }) );
              });

              // return promises for inserting the lines into diffLines
              return Promise.all(promises3);
            })
            .then( () => {
              checked.push(checklets);
              diffsArr.push({
                diffs: diffs,
                // diff already contains a path, but we need the old path as well as
                // the new one
                path: {
                  old: patch.oldFile().path(),
                  new: patch.newFile().path()
                },
                isModified: patch.isModified(),
              });
            }));
          });

          // return promises for inserting ALL the diffLines belonging to ALL the patches
          return Promise.all(promises2);
        }
      })
      .done( () => {

        /***
         * diffsArr: Array of diffFiles
         * diffFile: All the hunks from one single file
         *  |
         *  |-diffs: Array of diffs of ONE file
         *  |  diff: data of ONE hunk
         *  |   |
         *  |   |- header: string of header
         *  |   |- lines: array of Git line objects
         *  |   |- path: path of hunk
         *  |
         *  |-path: contains path of new and old file
         ***/

        this.setState({
          diffsArr: diffsArr,
          checked: checked
        });
      });
    }
  }

  callbackLine = (isChecked, fileIndex, diffIndex, lineIndex) => {
    this.setState((prevState) => {
      prevState.checked[fileIndex][diffIndex][lineIndex] = isChecked;
      return {
        checked: prevState.checked
      };
    })
  }

  callbackHunk = (isChecked, fileIndex, diffIndex) => {
    this.setState( (prevState) => {
      let checked = prevState.checked;
      checked[fileIndex][diffIndex].forEach( (line, lineIndex) => {
        checked[fileIndex][diffIndex][lineIndex] = isChecked;
      });
      return {
        checked: checked
      };
    })
  }

  componentDidMount = () => {
    this.formatDiffs(this.props);
  }

  componentWillReceiveProps = (newprops) => {
    this.formatDiffs(newprops);
  }

  render = () => {

    const diffStyle = this.props.diffStyle;

    let diffTree = [];
    let diffSelect = [];

    this.state.diffsArr.forEach((diffFile, fileIndex) => {
      let { diffs, path , isModified} = diffFile;

      let formattedLines = [];

      diffs.forEach( (diff, diffIndex) => {
        let { header, lines, path } = diff;

        formattedLines.push(header);
        lines.forEach( (line) => {
            formattedLines.push(String.fromCharCode(line.origin())+line.content());
        });
      });

      // collecting all the header and lines of the hunks in a single file
      // and putting them in a diffDisplay object
      const styles={
        span: {
          fontWeight: 600,
          color: diffStyle.type.color,
        },
        file: {
          color: diffStyle.file.color,
        }
      }
      let diffDisplay = {
        lines:formattedLines,
        path:

                 old
                {path.old}
                 new
                {path.new}

,
      };
      if(isModified) {
        diffTree.unshift(<Diff diff={diffDisplay} key={fileIndex} diffStyle={diffStyle} />);

        // creating a column of checboxes to select individual lines/hunks
        diffSelect.unshift(

        // each div contains all the checkboxes of a single file

          {diffs.map( (diff, diffIndex) => {
            let { header, lines, path } = diff;

            const styles = {
              checkbox: {
                height: 13,
                padding: 0,
                margin: 0,
                marginTop: 3,
                marginBottom: 2,
                marginLeft: 3,
                marginRight: -5,
              }
            };
            // the header calls the callback with an array containing all the difflines
            // and the line calls the callback with its corresponding diffline
            return (

              <input type='checkbox' style={styles.checkbox}
              onChange={(e) => {this.callbackHunk(e.target.checked,fileIndex, diffIndex);}} />
              {lines.map( (line, lineIndex) => {
                return <input type='checkbox' style={styles.checkbox}
                key={lineIndex}
                checked={this.state.checked[fileIndex][diffIndex][lineIndex]}
                onChange={(e) => {this.callbackLine(e.target.checked, fileIndex, diffIndex, lineIndex);}} />;
              })}

            );

          })}

        );
      }
      else {
        diffTree.push(<Diff diff={diffDisplay} key={fileIndex} diffStyle={diffStyle} />);
      }

    });

    const styles = {
      main: {
        display: 'flex',
        width: '100%',
      },
      diffTree: {
        width: '100%',
        border: '1px solid black',
      }
    };
    return(

        {this.props.showSelect?

{diffSelect}
:
        null}

{diffTree}

    );
  }
}

export default DiffPanel;<|endoftext|>