<commit_before>/*
 *  Project: Long Press
 *  Description: Pops a list of alternate characters when a key is long-pressed
 *  Author: Quentin Thiaucourt, http://toki-woki.net
 *    Licence: MIT License http://opensource.org/licenses/mit-license.php
 *
 *  Modified Oct 2014 to work with editable divs also
 *  Modified August 2015 to remove arrow key feature, which was interfering with ckeditor (or vice-versa, really)
 *  Modified August 2015 to add instructions at the bottom
 */

;(function ($, window, undefined) {

    var pluginName = 'longPress',
        document = window.document,
        defaults = {
            instructions: ""
        };

    var moreChars={
        // extended latin (and african latin)
        // upper
        'A':'ĀĂÀÁÂÃÄÅĄⱭ∀Æ',
        'B':'Ɓ',
        'C': 'ÇĆĈƆ̃ĊČƆ',
        'D':'ÐĎĐḎƊ',
        'E': 'ÈÉÊẼËĒĖĘẸĚƏÆƎƐ€',
        'F':'ƑƩ',
        'G':'ĜĞĠĢƢ',
        'H':'ĤĦ',
        'I':'ÌÍȊĬÎǏÏḮĨȈĮĪỈỊḬƗİĲ',
        'J':'ĴĲ',
        'K':'ĶƘ',
        'L':'ĹĻĽŁΛ',
        'N':'ÑŃŅŇŊƝ₦',
        'O':'ÒÓÔÕÖŌØŐŒƠƟ',
        'P':'Ƥ¶',
        'R':'ŔŘɌⱤ',
        'S':'ßſŚŜŞṢŠÞ§',
        'T':'ŢŤṮƬƮ',
        'U':'ÙÚÛŨÜŪŬŮŰŲɄƯƱ',
        'V':'Ʋ',
        'W':'ŴẄΩ',
        'Y':'ÝŶŸƔƳ',
        'Z':'ŹŻŽƵƷẔ',

        // lower
        'a':'āăàáâãäåąɑæαª',
        'b':'ßβɓ',
        'c': 'çςćĉɔ̃ċč¢ɔ',
        'd':'ðďđɖḏɖɗ',
        'e':'èéêẽëēėęẹěəæεɛ€',
        'f':'ƒʃƭ',
        'g':'ĝğġģɠƣ',
        'h':'ĥħɦẖ',
        'i':'ìíȋĭîǐïḯĩȉįīỉịḭɨıĳɪᵻᶖι',
        'j':'ĵɟʄĳ',
        'k':'ķƙ',
        'l':'ĺļľłλ',
        'n':'ñńņňŋɲ',
        'o':'òóôõöōøőœơɵ°',
        'p':'ƥ¶',
        'r':'ŕřɍɽ',
        's':'ßſśŝşṣšþ§',
        't':'ţťṯƭʈ',
        'u': 'ùúûũüūŭůűųưμυʉʊ',
        'v':'ʋ',
        'w':'ŵẅω',
        'y':'ýŷÿɣyƴ',
        'z':'źżžƶẕʒƹ',

        // Misc
        '$':'£¥€₩₨₳Ƀ¤',
        '!':'¡‼‽',
        '?':'¿‽',
        '%':'‰',
        '.':'…•',
        '-':'±‐–—',
        '+':'±†‡',
        '\\':'′″‴‘’‚‛',
        '"':'“”„‟',
        '<':'≤‹',
        '>':'≥›',
        '=':'≈≠≡'

    };
    // http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
    // 8  backspace
    // 9  tab
    // 13 enter
    // 16 shift (by itself; it still works to hold down capital letters)
    // 17 ctrl
    // 18 alt
    // 27 escape
    // 33 page up
    // 34 page down
    // 35 end
    // 36 home
    // 37 left arrow
    // 38 up arrow
    // 39 right arrow
    // 40 down arrow
    // 45 insert
    // 46 delete
    // Review: there are others we could add, function keys, num lock, scroll lock, break, forward & back slash, etc.
    //  not sure how much we gain from that...
    var ignoredKeyDownKeys=[8, 9, 13, 16, 17, 18, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];
    var ignoredKeyUpKeys = [8, 9, 13, /*16,*/ 17, 18, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];

    var selectedCharIndex;
    var lastWhich;
    var timer;
    var activeElement;
    var textAreaCaretPosition;
    var storedOffset;

    var popup;

    $(window).mousewheel(onWheel);

    function onKeyDown(e) {
        /* we had to disable thes because ckeditor was seeing them and messing things up. Hopefully in the future it can be reinstated:
        // Arrow key with popup visible
        if ($('.long-press-popup').length > 0 && (e.which == 37 || e.which == 39)) {
            e.stopPropagation(); //stop ckeditor from seeing this (DOESN"T WORK)
            e.preventDefault();
            if (e.which == 37) activePreviousLetter();
            else if (e.which==39) activateNextLetter();

            return;
        }
        */

        if (ignoredKeyDownKeys.indexOf(e.which)>-1) return;
        activeElement=e.target;

        if (e.which==lastWhich) {
            e.preventDefault();
            e.stopPropagation(); //attempt to stop ckeditor from seeing this event
            if (!timer) timer=setTimeout(onTimer, 10);
            return;
        }
        lastWhich=e.which;
    }
    function onKeyUp(e) {
        if (ignoredKeyUpKeys.indexOf(e.which) > -1) return;
        if (activeElement == null) return;

        // allow them to hold down the shift key after pressing a letter,
        // then use their other hand to do mouse or arrow keys.
        if (e.shiftKey) return;

        lastWhich=null;
        clearTimeout(timer);
        timer=null;

        hidePopup();
    }
    function onTimer() {
        var typedChar = isTextArea() ?
            $(activeElement).val().split('')[getTextAreaCaretPosition(activeElement)-1] :
            $(activeElement).text().split('')[getCaretPositionOffset(activeElement)-1];

        if (moreChars[typedChar]) {
            storeCaretPosition();
            showPopup((moreChars[typedChar]));
        } else {
            hidePopup();
        }
    }
    function showPopup(chars) {
        popup.find('ul').empty();
        var letter;
        for (var i=0; i<chars.length; i++) {
            letter=$('
').text(chars[i]);
            letter.mouseenter(activateLetter);
            letter.click(onPopupLetterClick);
            popup.find('ul').append(letter);
        }
        $('body').append(popup);
        selectedCharIndex=-1;
    }
    function onPopupLetterClick(e) {
        restoreCaretPosition();
        hidePopup();
    }
    function activateLetter(e) {
        selectCharIndex($(e.target).index());
    }
    function activateRelativeLetter(i) {
        selectCharIndex(($('.long-press-letter').length+selectedCharIndex+i) % $('.long-press-letter').length);
    }
    function activateNextLetter() {
        activateRelativeLetter(1);
    }
    function activePreviousLetter() {
        activateRelativeLetter(-1);
    }
    function hidePopup() {
        popup.detach();
    }
    function onWheel(e, delta, deltaX, deltaY) {
        if ($('.long-press-popup').length==0) return;
        e.preventDefault();
        delta<0 ? activateNextLetter() : activePreviousLetter();
    }
    function selectCharIndex(i) {
        $('.long-press-letter.selected').removeClass('selected');
        $('.long-press-letter').eq(i).addClass('selected');
        selectedCharIndex=i;
        updateChar();
    }

    function updateChar() {
        var newChar=$('.long-press-letter.selected').text();
        replacePreviousLetterWithText(newChar);
    }

    function isTextArea() {
        return $(activeElement).is('textarea');
    }

    function storeCaretPosition() {
        if (isTextArea()) {
            textAreaCaretPosition = getTextAreaCaretPosition(activeElement);
        } else {
            storedOffset = EditableDivUtils.getElementSelectionIndex(activeElement);
        }
    }

    function restoreCaretPosition() {
        if (isTextArea()) {
            setTextAreaCaretPosition(activeElement, textAreaCaretPosition);
        } else {
            EditableDivUtils.makeSelectionIn(activeElement, storedOffset, null, true);
            setFocusDelayed();
        }
    }

    function setFocusDelayed() {
        window.setTimeout(function() {
            if (activeElement && typeof activeElement.focus != "undefined") {
                activeElement.focus();
            }
        }, 1);
    }

    function replacePreviousLetterWithText(text) {
        if (isTextArea()) {
            var pos=getTextAreaCaretPosition(activeElement);
            var arVal=$(activeElement).val().split('');
            arVal[pos-1]=text;
            $(activeElement).val(arVal.join(''));
            setTextAreaCaretPosition(activeElement, pos);
        } else {
            var sel, textNode, clone;
            var range = getCaretPosition();
            if (window.getSelection && range && range.startOffset != 0) {
                sel = window.getSelection();
                if (sel.getRangeAt && sel.rangeCount) {
                    textNode = document.createTextNode(text);

                    clone = range.cloneRange();
                    clone.setStart(range.startContainer, range.startOffset - 1);
                    clone.setEnd(range.startContainer, range.startOffset);
                    clone.deleteContents();
                    range.insertNode(textNode);

                    // Move caret to the end of the newly inserted text node
                    range.setStart(textNode, textNode.length);
                    range.setEnd(textNode, textNode.length);

                    sel.removeAllRanges();
                    sel.addRange(range);
                }
            }
        }
    }

    function getCaretPosition() {
        var sel = window.getSelection();
        return sel.getRangeAt(0);
    }

    function getCaretPositionOffset(element) {
        var caretOffset = 0;
        var doc = element.ownerDocument || element.document;
        var win = doc.defaultView || doc.parentWindow;
        var sel;
        if (typeof win.getSelection != "undefined") {
            sel = win.getSelection();
            if (sel.rangeCount > 0) {
                var range = win.getSelection().getRangeAt(0);
                var preCaretRange = range.cloneRange();
                preCaretRange.selectNodeContents(element);
                preCaretRange.setEnd(range.endContainer, range.endOffset);
                caretOffset = preCaretRange.toString().length;
            }
        } else if ( (sel = doc.selection) && sel.type != "Control") {
            var textRange = sel.createRange();
            var preCaretTextRange = doc.body.createTextRange();
            preCaretTextRange.moveToElementText(element);
            preCaretTextRange.setEndPoint("EndToEnd", textRange);
            caretOffset = preCaretTextRange.text.length;
        }
        return caretOffset;
    }

    function getTextAreaCaretPosition (ctrl) {
        var caretPos = 0;
        if (document.selection) {
            // IE Support
            ctrl.focus ();
            var sel = document.selection.createRange ();
            sel.moveStart ('character', -ctrl.value.length);
            caretPos = sel.text.length;
        } else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
            // Firefox support
            caretPos = ctrl.selectionStart;
        }
        return caretPos;
    }
    function setTextAreaCaretPosition(ctrl, pos) {
        if (ctrl.setSelectionRange) {
            ctrl.focus();
            ctrl.setSelectionRange(pos,pos);
        } else if (ctrl.createTextRange) {
            var range = ctrl.createTextRange();
            range.collapse(true);
            range.moveEnd('character', pos);
            range.moveStart('character', pos);
            range.select();
        }
    }

    function LongPress( element, options ) {

        this.element = element;
        this.options = $.extend( {}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        popup = $('

' + this.options.instructions + '
');
        this.init();
    }

    LongPress.prototype = {

        init: function () {
            $(this.element).keydown(onKeyDown);
            $(this.element).keyup(onKeyUp);
        }

    };

    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new LongPress( this, options ));
            }
        });
    };

}(jQuery, window));<commit_msg>Set selection properly when needlessly clicking on longpress (BL-2717)<commit_after>/*
 *  Project: Long Press
 *  Description: Pops a list of alternate characters when a key is long-pressed
 *  Author: Quentin Thiaucourt, http://toki-woki.net
 *    Licence: MIT License http://opensource.org/licenses/mit-license.php
 *
 *  Modified Oct 2014 to work with editable divs also
 *  Modified August 2015 to remove arrow key feature, which was interfering with ckeditor (or vice-versa, really)
 *  Modified August 2015 to add instructions at the bottom
 *  Modified September 2015 to set focus before selection in restoreCaretPosition()
 */

;(function ($, window, undefined) {

    var pluginName = 'longPress',
        document = window.document,
        defaults = {
            instructions: ""
        };

    var moreChars={
        // extended latin (and african latin)
        // upper
        'A':'ĀĂÀÁÂÃÄÅĄⱭ∀Æ',
        'B':'Ɓ',
        'C': 'ÇĆĈƆ̃ĊČƆ',
        'D':'ÐĎĐḎƊ',
        'E': 'ÈÉÊẼËĒĖĘẸĚƏÆƎƐ€',
        'F':'ƑƩ',
        'G':'ĜĞĠĢƢ',
        'H':'ĤĦ',
        'I':'ÌÍȊĬÎǏÏḮĨȈĮĪỈỊḬƗİĲ',
        'J':'ĴĲ',
        'K':'ĶƘ',
        'L':'ĹĻĽŁΛ',
        'N':'ÑŃŅŇŊƝ₦',
        'O':'ÒÓÔÕÖŌØŐŒƠƟ',
        'P':'Ƥ¶',
        'R':'ŔŘɌⱤ',
        'S':'ßſŚŜŞṢŠÞ§',
        'T':'ŢŤṮƬƮ',
        'U':'ÙÚÛŨÜŪŬŮŰŲɄƯƱ',
        'V':'Ʋ',
        'W':'ŴẄΩ',
        'Y':'ÝŶŸƔƳ',
        'Z':'ŹŻŽƵƷẔ',

        // lower
        'a':'āăàáâãäåąɑæαª',
        'b':'ßβɓ',
        'c': 'çςćĉɔ̃ċč¢ɔ',
        'd':'ðďđɖḏɖɗ',
        'e':'èéêẽëēėęẹěəæεɛ€',
        'f':'ƒʃƭ',
        'g':'ĝğġģɠƣ',
        'h':'ĥħɦẖ',
        'i':'ìíȋĭîǐïḯĩȉįīỉịḭɨıĳɪᵻᶖι',
        'j':'ĵɟʄĳ',
        'k':'ķƙ',
        'l':'ĺļľłλ',
        'n':'ñńņňŋɲ',
        'o':'òóôõöōøőœơɵ°',
        'p':'ƥ¶',
        'r':'ŕřɍɽ',
        's':'ßſśŝşṣšþ§',
        't':'ţťṯƭʈ',
        'u': 'ùúûũüūŭůűųưμυʉʊ',
        'v':'ʋ',
        'w':'ŵẅω',
        'y':'ýŷÿɣyƴ',
        'z':'źżžƶẕʒƹ',

        // Misc
        '$':'£¥€₩₨₳Ƀ¤',
        '!':'¡‼‽',
        '?':'¿‽',
        '%':'‰',
        '.':'…•',
        '-':'±‐–—',
        '+':'±†‡',
        '\\':'′″‴‘’‚‛',
        '"':'“”„‟',
        '<':'≤‹',
        '>':'≥›',
        '=':'≈≠≡'

    };
    // http://www.cambiaresearch.com/articles/15/javascript-char-codes-key-codes
    // 8  backspace
    // 9  tab
    // 13 enter
    // 16 shift (by itself; it still works to hold down capital letters)
    // 17 ctrl
    // 18 alt
    // 27 escape
    // 33 page up
    // 34 page down
    // 35 end
    // 36 home
    // 37 left arrow
    // 38 up arrow
    // 39 right arrow
    // 40 down arrow
    // 45 insert
    // 46 delete
    // Review: there are others we could add, function keys, num lock, scroll lock, break, forward & back slash, etc.
    //  not sure how much we gain from that...
    var ignoredKeyDownKeys=[8, 9, 13, 16, 17, 18, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];
    var ignoredKeyUpKeys = [8, 9, 13, /*16,*/ 17, 18, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];

    var selectedCharIndex;
    var lastWhich;
    var timer;
    var activeElement;
    var textAreaCaretPosition;
    var storedOffset;

    var popup;

    $(window).mousewheel(onWheel);

    function onKeyDown(e) {
        /* we had to disable thes because ckeditor was seeing them and messing things up. Hopefully in the future it can be reinstated:
        // Arrow key with popup visible
        if ($('.long-press-popup').length > 0 && (e.which == 37 || e.which == 39)) {
            e.stopPropagation(); //stop ckeditor from seeing this (DOESN"T WORK)
            e.preventDefault();
            if (e.which == 37) activePreviousLetter();
            else if (e.which==39) activateNextLetter();

            return;
        }
        */

        if (ignoredKeyDownKeys.indexOf(e.which)>-1) return;
        activeElement=e.target;

        if (e.which==lastWhich) {
            e.preventDefault();
            e.stopPropagation(); //attempt to stop ckeditor from seeing this event
            if (!timer) timer=setTimeout(onTimer, 10);
            return;
        }
        lastWhich=e.which;
    }
    function onKeyUp(e) {
        if (ignoredKeyUpKeys.indexOf(e.which) > -1) return;
        if (activeElement == null) return;

        // allow them to hold down the shift key after pressing a letter,
        // then use their other hand to do mouse or arrow keys.
        if (e.shiftKey) return;

        lastWhich=null;
        clearTimeout(timer);
        timer=null;

        hidePopup();
    }
    function onTimer() {
        var typedChar = isTextArea() ?
            $(activeElement).val().split('')[getTextAreaCaretPosition(activeElement)-1] :
            $(activeElement).text().split('')[getCaretPositionOffset(activeElement)-1];

        if (moreChars[typedChar]) {
            storeCaretPosition();
            showPopup((moreChars[typedChar]));
        } else {
            hidePopup();
        }
    }
    function showPopup(chars) {
        popup.find('ul').empty();
        var letter;
        for (var i=0; i<chars.length; i++) {
            letter=$('
').text(chars[i]);
            letter.mouseenter(activateLetter);
            letter.click(onPopupLetterClick);
            popup.find('ul').append(letter);
        }
        $('body').append(popup);
        selectedCharIndex=-1;
    }
    function onPopupLetterClick(e) {
        restoreCaretPosition();
        hidePopup();
    }
    function activateLetter(e) {
        selectCharIndex($(e.target).index());
    }
    function activateRelativeLetter(i) {
        selectCharIndex(($('.long-press-letter').length+selectedCharIndex+i) % $('.long-press-letter').length);
    }
    function activateNextLetter() {
        activateRelativeLetter(1);
    }
    function activePreviousLetter() {
        activateRelativeLetter(-1);
    }
    function hidePopup() {
        popup.detach();
    }
    function onWheel(e, delta, deltaX, deltaY) {
        if ($('.long-press-popup').length==0) return;
        e.preventDefault();
        delta<0 ? activateNextLetter() : activePreviousLetter();
    }
    function selectCharIndex(i) {
        $('.long-press-letter.selected').removeClass('selected');
        $('.long-press-letter').eq(i).addClass('selected');
        selectedCharIndex=i;
        updateChar();
    }

    function updateChar() {
        var newChar=$('.long-press-letter.selected').text();
        replacePreviousLetterWithText(newChar);
    }

    function isTextArea() {
        return $(activeElement).is('textarea');
    }

    function storeCaretPosition() {
        if (isTextArea()) {
            textAreaCaretPosition = getTextAreaCaretPosition(activeElement);
        } else {
            storedOffset = EditableDivUtils.getElementSelectionIndex(activeElement);
        }
    }

    function restoreCaretPosition() {
        if (isTextArea()) {
            setTextAreaCaretPosition(activeElement, textAreaCaretPosition);
        } else {
            // If we make the selection before setting the focus, the selection
            // ends up in the wrong place (BL-2717).
            if (activeElement && typeof activeElement.focus != "undefined") {
                activeElement.focus();
                EditableDivUtils.makeSelectionIn(activeElement, storedOffset, null, true);
            }
        }
    }

    function setFocusDelayed() {
        window.setTimeout(function() {
            if (activeElement && typeof activeElement.focus != "undefined") {
                activeElement.focus();
            }
        }, 1);
    }

    function replacePreviousLetterWithText(text) {
        if (isTextArea()) {
            var pos=getTextAreaCaretPosition(activeElement);
            var arVal=$(activeElement).val().split('');
            arVal[pos-1]=text;
            $(activeElement).val(arVal.join(''));
            setTextAreaCaretPosition(activeElement, pos);
        } else {
            var sel, textNode, clone;
            var range = getCaretPosition();
            if (window.getSelection && range && range.startOffset != 0) {
                sel = window.getSelection();
                if (sel.getRangeAt && sel.rangeCount) {
                    textNode = document.createTextNode(text);

                    clone = range.cloneRange();
                    clone.setStart(range.startContainer, range.startOffset - 1);
                    clone.setEnd(range.startContainer, range.startOffset);
                    clone.deleteContents();
                    range.insertNode(textNode);

                    // Move caret to the end of the newly inserted text node
                    range.setStart(textNode, textNode.length);
                    range.setEnd(textNode, textNode.length);

                    sel.removeAllRanges();
                    sel.addRange(range);
                }
            }
        }
    }

    function getCaretPosition() {
        var sel = window.getSelection();
        return sel.getRangeAt(0);
    }

    function getCaretPositionOffset(element) {
        var caretOffset = 0;
        var doc = element.ownerDocument || element.document;
        var win = doc.defaultView || doc.parentWindow;
        var sel;
        if (typeof win.getSelection != "undefined") {
            sel = win.getSelection();
            if (sel.rangeCount > 0) {
                var range = win.getSelection().getRangeAt(0);
                var preCaretRange = range.cloneRange();
                preCaretRange.selectNodeContents(element);
                preCaretRange.setEnd(range.endContainer, range.endOffset);
                caretOffset = preCaretRange.toString().length;
            }
        } else if ( (sel = doc.selection) && sel.type != "Control") {
            var textRange = sel.createRange();
            var preCaretTextRange = doc.body.createTextRange();
            preCaretTextRange.moveToElementText(element);
            preCaretTextRange.setEndPoint("EndToEnd", textRange);
            caretOffset = preCaretTextRange.text.length;
        }
        return caretOffset;
    }

    function getTextAreaCaretPosition (ctrl) {
        var caretPos = 0;
        if (document.selection) {
            // IE Support
            ctrl.focus ();
            var sel = document.selection.createRange ();
            sel.moveStart ('character', -ctrl.value.length);
            caretPos = sel.text.length;
        } else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
            // Firefox support
            caretPos = ctrl.selectionStart;
        }
        return caretPos;
    }
    function setTextAreaCaretPosition(ctrl, pos) {
        if (ctrl.setSelectionRange) {
            ctrl.focus();
            ctrl.setSelectionRange(pos,pos);
        } else if (ctrl.createTextRange) {
            var range = ctrl.createTextRange();
            range.collapse(true);
            range.moveEnd('character', pos);
            range.moveStart('character', pos);
            range.select();
        }
    }

    function LongPress( element, options ) {

        this.element = element;
        this.options = $.extend( {}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        popup = $('

' + this.options.instructions + '
');
        this.init();
    }

    LongPress.prototype = {

        init: function () {
            $(this.element).keydown(onKeyDown);
            $(this.element).keyup(onKeyUp);
        }

    };

    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data(this, 'plugin_' + pluginName, new LongPress( this, options ));
            }
        });
    };

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


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 * @emails oncall+draft_js
 */

'use strict';

const onBeforeInput = require('editOnBeforeInput');
const onBlur = require('editOnBlur');
const onCompositionStart = require('editOnCompositionStart');
const onCopy = require('editOnCopy');
const onCut = require('editOnCut');
const onDragOver = require('editOnDragOver');
const onDragStart = require('editOnDragStart');
const onFocus = require('editOnFocus');
const onInput = require('editOnInput');
const onKeyDown = require('editOnKeyDown');
const onPaste = require('editOnPaste');
const onSelect = require('editOnSelect');

const DraftEditorEditHandler = {
  onBeforeInput,
  onBlur,
  onCompositionStart,
  onCopy,
  onCut,
  onDragOver,
  onDragStart,
  onFocus,
  onInput,
  onKeyDown,
  onPaste,
  onSelect,
};

module.exports = DraftEditorEditHandler;
<commit_msg>Fix for workchat composer cursor jumping<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 * @emails oncall+draft_js
 */

'use strict';

import type DraftEditor from 'DraftEditor.react';

const UserAgent = require('UserAgent');

const onBeforeInput = require('editOnBeforeInput');
const onBlur = require('editOnBlur');
const onCompositionStart = require('editOnCompositionStart');
const onCopy = require('editOnCopy');
const onCut = require('editOnCut');
const onDragOver = require('editOnDragOver');
const onDragStart = require('editOnDragStart');
const onFocus = require('editOnFocus');
const onInput = require('editOnInput');
const onKeyDown = require('editOnKeyDown');
const onPaste = require('editOnPaste');
const onSelect = require('editOnSelect');

const isChrome = UserAgent.isBrowser('Chrome');

const selectionHandler: (e: DraftEditor) => void = isChrome
  ? onSelect
  : e => {};

const DraftEditorEditHandler = {
  onBeforeInput,
  onBlur,
  onCompositionStart,
  onCopy,
  onCut,
  onDragOver,
  onDragStart,
  onFocus,
  onInput,
  onKeyDown,
  onPaste,
  onSelect,
  // In certain cases, contenteditable on chrome does not fire the onSelect
  // event, causing problems with cursor positioning. Therefore, the selection
  // state update handler is added to more events to ensure that the selection
  // state is always synced with the actual cursor positions.
  onMouseUp: selectionHandler,
  onKeyUp: selectionHandler,
};

module.exports = DraftEditorEditHandler;
<|endoftext|>


<commit_before>/**
 * @fileoverview Implementation of the HTMl5 Microdata specification.
 */
goog.provide('treesaver.microdata');

goog.require('treesaver.array');
goog.require('treesaver.dom');
goog.require('treesaver.string');

if (SUPPORT_MICRODATA && !treesaver.capabilities.SUPPORTS_MICRODATA) {

   /**
   * Returns the itemValue of an Element.
   *
   * @param {!Element} element The element to extract the itemValue for.
   * @return {!string} The itemValue of the element.
   */
  function getItemValue(element) {
    var elementName = element.nodeName;

    if (elementName === 'META') {
      return element.content;
    } else if (['AUDIO', 'EMBED', 'IFRAME',
                'IMG', 'SOURCE', 'VIDEO'].indexOf(elementName) !== -1) {
      return element.src;
    } else if (['A', 'AREA', 'LINK'].indexOf(elementName) !== -1) {
      return element.href;
    } else if (elementName === 'OBJECT') {
      return element.data;
    } else if (elementName === 'TIME' &&
                treesaver.dom.hasAttr(element, 'datetime')) {
      return element.dateTime;
    } else {
      return treesaver.dom.innerText(element);
    }
  }

  /**
   * Returns the properties for the given item.
   *
   * @param {!Element} item The item for which to find the properties.
   * @return {!Array} The properties for the item.
   */
  function getProperties(item) {
    var root = item,
        pending = [],
        properties = [],
        references = [],
        children = [],
        current;

    children = treesaver.array.toArray(root.childNodes);

    pending = children.filter(function(element) {
      return element.nodeType === 1;
    });

    if (treesaver.dom.hasAttr(root, 'itemref')) {
      references = root.getAttribute('itemref').trim().split(/\s+/);

      references.forEach(function(reference) {
        var element = document.getElementById(reference);

        if (element) {
          pending.push(element);
        }
      });
    }

    pending = pending.filter(function(candidate, index) {
      var scope = null,
          parent = candidate,
          ancestors = [];

      // Remove duplicates
      if (pending.indexOf(candidate) !== index &&
          pending.indexOf(candidate, index) !== -1) {
        return false;
      }

      while ((parent = parent.parentNode) !== null && parent.nodeType === 1) {
        ancestors.push(parent);
        if (treesaver.dom.hasAttr(parent, 'itemscope')) {
          scope = parent;
          break;
        }
      }

      if (scope !== null) {
        // If one of the other elements in pending is an ancestor element of
        // candidate, and that element is scope, then remove candidate from
        // pending.
        if (pending.indexOf(scope) !== -1) {
          return false;
        }

        // If one of the other elements in pending is an ancestor element of
        // candidate, and either scope is null or that element also has scope
        // as its nearest ancestor element with an itemscope attribute
        // specified, then remove candidate from pending.
        return !ancestors.some(function(ancestor) {
          var elementIndex = -1,
              elementParent,
              elementScope = null;

          // If ancestor is in pending
          if ((elementIndex = pending.indexOf(ancestor)) !== -1) {
            elementParent = pending[elementIndex];

            // Find the nearest ancestor element with an itemscope attribute
            while ((elementParent = elementParent.parentNode) !== null &&
                    elementParent.nodeType === 1) {
              if (treesaver.dom.hasAttr(elementParent, 'itemscope')) {
                elementScope = elementParent;
                break;
              }
            }
            // The nearest ancestor element equals scope
            if (elementScope === scope) {
              return true;
            }
          }
          return false;
        });
      }
      return true;
    });

    pending.sort(function(a, b) {
      return 3 - (treesaver.dom.compareDocumentPosition(b, a) & 6);
    });

    while ((current = pending.pop())) {
      if (treesaver.dom.hasAttr(current, 'itemprop')) {
        properties.push(current);

        // This is a necessary deviation from the normal algorithm because
        // we can not modify the Element prototype in IE7, so we recursively
        // calculate the properties for each property that has an itemscope.
        if (treesaver.dom.hasAttr(current, 'itemscope')) {
          current['itemScope'] = true;
          current['properties'] = getProperties(current);
        }
      }
      if (!treesaver.dom.hasAttr(current, 'itemscope')) {
        // Push all the child elements of current onto pending, in tree order
        // (so the first child of current will be the next element to be
        // popped from pending).
        children = treesaver.array.toArray(current.childNodes).reverse();
        children.forEach(function(child) {
          if (child.nodeType === 1) {
            pending.push(child);
          }
        });
      }
    }

    properties.forEach(function(property) {
      // Attach the (none-live) itemValue attribute to the element
      if (treesaver.dom.hasAttr(property, 'itemscope')) {
        property['itemValue'] = property;
      } else {
        property['itemValue'] = getItemValue(property);
      }

      property['itemProp'] = property.getAttribute('itemprop');
    });

    return properties;
  }

  /**
   * Returns an Array of the elements in the Document that create items,
   * that are not part of other items, and that are of one of the types
   * given in the argument, if any are listed.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A non-live Array of elements.
   */
  function getItems(types, root) {
    var items = [];

    if (types && /\S/.test(types)) {
      types = types.trim().split(/\s+/);
    } else {
      types = [];
    }

    // Retrieve all microdata items
    items = treesaver.dom.getElementsByProperty('itemscope', null, null, root);

    // Filter out top level items, and optionally items that match
    // the given types.
    items = items.filter(function(item) {
      if (!treesaver.dom.hasAttr(item, 'itemprop')) {
        if (types.length === 0 ||
            (treesaver.dom.hasAttr(item, 'itemtype') &&
             types.indexOf(item.getAttribute('itemtype')) !== -1)) {

          item['itemScope'] = true;

          // Attach the (none-live) properties attribute to the element
          item['properties'] = getProperties(item);

          if (treesaver.dom.hasAttr(item, 'itemid')) {
            item['itemId'] = item.getAttribute('itemid');
          }

          if (treesaver.dom.hasAttr(item, 'itemref')) {
            item['itemRef'] = item.getAttribute('itemRef');
          }

          if (treesaver.dom.hasAttr(item, 'itemtype')) {
            item['itemType'] = item.getAttribute('itemtype');
          }

          return true;
        }
      }
      return false;
    });
    return items;
  }
  document['getItems'] = getItems;
}

// This code assumes the microdata API is available. Either
// the implementation above, or a native one.
if (SUPPORT_MICRODATA) {
  /**
   * Returns the JSON representation of a microdata item.
   *
   * @private
   * @param {!Element} item The element to generate the representation for.
   * @return {!Object} The JSON representation of an item.
   */
  treesaver.microdata.getObject_ = function(item) {
    var result = {},
        properties = {};

    if (item.itemType) {
      result['type'] = item.itemType;
    }
    if (item.itemId) {
      result['id'] = item.itemId;
    }
    item.properties.forEach(function(property) {
      var value = property.itemValue,
          names = [];

      // If value is an item (i.e. value has an itemScope attribute)
      if (value.itemScope) {
        value = treesaver.microdata.getObject_(value);
      }

      names = property.itemProp.split(/\s+/g);

      names.forEach(function(n) {
        if (!properties.hasOwnProperty(n)) {
          properties[n] = [];
        }
        properties[n].push(value);
      });
    });
    result['properties'] = properties;
    return result;
  };

  /**
   * Returns an Array of the elements in the document or descendants of
   * the root node that create items, that are not part of other items,
   * and that are of one of the types given in the argument, if any are
   * listed.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A non-live Array of elements.
   */
  treesaver.microdata.getItems = function(types, root) {
    if (treesaver.capabilities.SUPPORTS_MICRODATA) {
      // Fake the root parameter by filtering out microdata items
      // based on their ancestors.
      var items = treesaver.array.toArray(document.getItems(types));

      if (!root) {
        return items;
      }

      return items.filter(function(item) {
        var parent = item;

        while ((parent = parent.parentNode) !== null && parent.nodeType === 1) {
          if (parent === root) {
            return true;
          }
        }
        return false;
      });
    } else {
      // We are using our own microdata implementation,
      // which supports the context parameter.
      return document.getItems(types, root);
    }
  };

  /**
   * Returns a JSON representation of the microdata items
   * that match the given types.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A Array of Objects representing micro-
   * data items.
   */
  treesaver.microdata.getJSONItems = function(types, root) {
    var items = treesaver.microdata.getItems(types, root);
    return items.map(function(item) {
      return treesaver.microdata.getObject_(item);
    });
  };
}
<commit_msg>Changed ancestor check to a contains call.<commit_after>/**
 * @fileoverview Implementation of the HTMl5 Microdata specification.
 */
goog.provide('treesaver.microdata');

goog.require('treesaver.array');
goog.require('treesaver.dom');
goog.require('treesaver.string');

if (SUPPORT_MICRODATA && !treesaver.capabilities.SUPPORTS_MICRODATA) {

   /**
   * Returns the itemValue of an Element.
   *
   * @param {!Element} element The element to extract the itemValue for.
   * @return {!string} The itemValue of the element.
   */
  function getItemValue(element) {
    var elementName = element.nodeName;

    if (elementName === 'META') {
      return element.content;
    } else if (['AUDIO', 'EMBED', 'IFRAME',
                'IMG', 'SOURCE', 'VIDEO'].indexOf(elementName) !== -1) {
      return element.src;
    } else if (['A', 'AREA', 'LINK'].indexOf(elementName) !== -1) {
      return element.href;
    } else if (elementName === 'OBJECT') {
      return element.data;
    } else if (elementName === 'TIME' &&
                treesaver.dom.hasAttr(element, 'datetime')) {
      return element.dateTime;
    } else {
      return treesaver.dom.innerText(element);
    }
  }

  /**
   * Returns the properties for the given item.
   *
   * @param {!Element} item The item for which to find the properties.
   * @return {!Array} The properties for the item.
   */
  function getProperties(item) {
    var root = item,
        pending = [],
        properties = [],
        references = [],
        children = [],
        current;

    children = treesaver.array.toArray(root.childNodes);

    pending = children.filter(function(element) {
      return element.nodeType === 1;
    });

    if (treesaver.dom.hasAttr(root, 'itemref')) {
      references = root.getAttribute('itemref').trim().split(/\s+/);

      references.forEach(function(reference) {
        var element = document.getElementById(reference);

        if (element) {
          pending.push(element);
        }
      });
    }

    pending = pending.filter(function(candidate, index) {
      var scope = null,
          parent = candidate,
          ancestors = [];

      // Remove duplicates
      if (pending.indexOf(candidate) !== index &&
          pending.indexOf(candidate, index) !== -1) {
        return false;
      }

      while ((parent = parent.parentNode) !== null && parent.nodeType === 1) {
        ancestors.push(parent);
        if (treesaver.dom.hasAttr(parent, 'itemscope')) {
          scope = parent;
          break;
        }
      }

      if (scope !== null) {
        // If one of the other elements in pending is an ancestor element of
        // candidate, and that element is scope, then remove candidate from
        // pending.
        if (pending.indexOf(scope) !== -1) {
          return false;
        }

        // If one of the other elements in pending is an ancestor element of
        // candidate, and either scope is null or that element also has scope
        // as its nearest ancestor element with an itemscope attribute
        // specified, then remove candidate from pending.
        return !ancestors.some(function(ancestor) {
          var elementIndex = -1,
              elementParent,
              elementScope = null;

          // If ancestor is in pending
          if ((elementIndex = pending.indexOf(ancestor)) !== -1) {
            elementParent = pending[elementIndex];

            // Find the nearest ancestor element with an itemscope attribute
            while ((elementParent = elementParent.parentNode) !== null &&
                    elementParent.nodeType === 1) {
              if (treesaver.dom.hasAttr(elementParent, 'itemscope')) {
                elementScope = elementParent;
                break;
              }
            }
            // The nearest ancestor element equals scope
            if (elementScope === scope) {
              return true;
            }
          }
          return false;
        });
      }
      return true;
    });

    pending.sort(function(a, b) {
      return 3 - (treesaver.dom.compareDocumentPosition(b, a) & 6);
    });

    while ((current = pending.pop())) {
      if (treesaver.dom.hasAttr(current, 'itemprop')) {
        properties.push(current);

        // This is a necessary deviation from the normal algorithm because
        // we can not modify the Element prototype in IE7, so we recursively
        // calculate the properties for each property that has an itemscope.
        if (treesaver.dom.hasAttr(current, 'itemscope')) {
          current['itemScope'] = true;
          current['properties'] = getProperties(current);
        }
      }
      if (!treesaver.dom.hasAttr(current, 'itemscope')) {
        // Push all the child elements of current onto pending, in tree order
        // (so the first child of current will be the next element to be
        // popped from pending).
        children = treesaver.array.toArray(current.childNodes).reverse();
        children.forEach(function(child) {
          if (child.nodeType === 1) {
            pending.push(child);
          }
        });
      }
    }

    properties.forEach(function(property) {
      // Attach the (none-live) itemValue attribute to the element
      if (treesaver.dom.hasAttr(property, 'itemscope')) {
        property['itemValue'] = property;
      } else {
        property['itemValue'] = getItemValue(property);
      }

      property['itemProp'] = property.getAttribute('itemprop');
    });

    return properties;
  }

  /**
   * Returns an Array of the elements in the Document that create items,
   * that are not part of other items, and that are of one of the types
   * given in the argument, if any are listed.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A non-live Array of elements.
   */
  function getItems(types, root) {
    var items = [];

    if (types && /\S/.test(types)) {
      types = types.trim().split(/\s+/);
    } else {
      types = [];
    }

    // Retrieve all microdata items
    items = treesaver.dom.getElementsByProperty('itemscope', null, null, root);

    // Filter out top level items, and optionally items that match
    // the given types.
    items = items.filter(function(item) {
      if (!treesaver.dom.hasAttr(item, 'itemprop')) {
        if (types.length === 0 ||
            (treesaver.dom.hasAttr(item, 'itemtype') &&
             types.indexOf(item.getAttribute('itemtype')) !== -1)) {

          item['itemScope'] = true;

          // Attach the (none-live) properties attribute to the element
          item['properties'] = getProperties(item);

          if (treesaver.dom.hasAttr(item, 'itemid')) {
            item['itemId'] = item.getAttribute('itemid');
          }

          if (treesaver.dom.hasAttr(item, 'itemref')) {
            item['itemRef'] = item.getAttribute('itemRef');
          }

          if (treesaver.dom.hasAttr(item, 'itemtype')) {
            item['itemType'] = item.getAttribute('itemtype');
          }

          return true;
        }
      }
      return false;
    });
    return items;
  }
  document['getItems'] = getItems;
}

// This code assumes the microdata API is available. Either
// the implementation above, or a native one.
if (SUPPORT_MICRODATA) {
  /**
   * Returns the JSON representation of a microdata item.
   *
   * @private
   * @param {!Element} item The element to generate the representation for.
   * @return {!Object} The JSON representation of an item.
   */
  treesaver.microdata.getObject_ = function(item) {
    var result = {},
        properties = {};

    if (item.itemType) {
      result['type'] = item.itemType;
    }
    if (item.itemId) {
      result['id'] = item.itemId;
    }
    item.properties.forEach(function(property) {
      var value = property.itemValue,
          names = [];

      // If value is an item (i.e. value has an itemScope attribute)
      if (value.itemScope) {
        value = treesaver.microdata.getObject_(value);
      }

      names = property.itemProp.split(/\s+/g);

      names.forEach(function(n) {
        if (!properties.hasOwnProperty(n)) {
          properties[n] = [];
        }
        properties[n].push(value);
      });
    });
    result['properties'] = properties;
    return result;
  };

  /**
   * Returns an Array of the elements in the document or descendants of
   * the root node that create items, that are not part of other items,
   * and that are of one of the types given in the argument, if any are
   * listed.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A non-live Array of elements.
   */
  treesaver.microdata.getItems = function(types, root) {
    if (treesaver.capabilities.SUPPORTS_MICRODATA) {
      // Fake the root parameter by filtering out microdata items
      // based on their ancestors.
      var items = treesaver.array.toArray(document.getItems(types));

      if (!root) {
        return items;
      }

      return items.filter(function(item) {
        return root.contains(item);
      });
    } else {
      // We are using our own microdata implementation,
      // which supports the context parameter.
      return document.getItems(types, root);
    }
  };

  /**
   * Returns a JSON representation of the microdata items
   * that match the given types.
   *
   * @param {string=} types A space-separated list of types.
   * @param {HTMLDocument|Element=} root The root element to use as
   * context.
   * @return {!Array} A Array of Objects representing micro-
   * data items.
   */
  treesaver.microdata.getJSONItems = function(types, root) {
    var items = treesaver.microdata.getItems(types, root);
    return items.map(function(item) {
      return treesaver.microdata.getObject_(item);
    });
  };
}
<|endoftext|>


<commit_before>import './index.scss';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { List } from 'immutable';

const propTypes = {
  socialLinks: PropTypes.instanceOf(List),
  fetchSocialLinks: PropTypes.func
};

export class SocialLinks extends Component {
  componentWillMount () {
    return this.props.fetchSocialLinks();
  }

  pickIcon (site) {
    switch (site) {
      case 'github':
        return ;
      case 'twitter':
        return ;
      case 'linkedin':
        return ;
      case 'instagram':
        return ;
      case 'dribbble':
        return ;
      case 'gitlab':
        return ;
      case 'medium':
        return ;
      default:
        return null;
    }
  }

  displaySocialLink (socialLink) {
    return (

        {this.pickIcon(socialLink.getIn(['attributes', 'site']))}

    );
  }

  render () {
    return (

        {this.props.socialLinks.map((socialLink) => {
          return this.displaySocialLink(socialLink);
        })}

    );
  }
}

SocialLinks.propTypes = propTypes;

export default SocialLinks;
<commit_msg>added medium for real<commit_after>import './index.scss';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { List } from 'immutable';

const propTypes = {
  socialLinks: PropTypes.instanceOf(List),
  fetchSocialLinks: PropTypes.func
};

export class SocialLinks extends Component {
  componentWillMount () {
    return this.props.fetchSocialLinks();
  }

  pickIcon (site) {
    switch (site) {
      case 'github':
        return ;
      case 'twitter':
        return ;
      case 'linkedin':
        return ;
      case 'instagram':
        return ;
      case 'dribbble':
        return ;
      case 'gitlab':
        return ;
      case 'medium':
        return ;
      default:
        return null;
    }
  }

  displaySocialLink (socialLink) {
    return (

        {this.pickIcon(socialLink.getIn(['attributes', 'site']))}

    );
  }

  render () {
    return (

        {this.props.socialLinks.map((socialLink) => {
          return this.displaySocialLink(socialLink);
        })}

          {this.pickIcon('medium')}

    );
  }
}

SocialLinks.propTypes = propTypes;

export default SocialLinks;
<|endoftext|>


<commit_before>require('../../stylus/components/_navigation-drawer.styl')

// Mixins
import Applicationable from '../../mixins/applicationable'
import Overlayable from '../../mixins/overlayable'
import SSRBootable from '../../mixins/ssr-bootable'
import Themeable from '../../mixins/themeable'

// Directives
import ClickOutside from '../../directives/click-outside'
import Resize from '../../directives/resize'
import Touch from '../../directives/touch'

export default {
  name: 'v-navigation-drawer',

  mixins: [
    Applicationable(null, [
      'miniVariant',
      'right',
      'width'
    ]),
    Overlayable,
    SSRBootable,
    Themeable
  ],

  directives: {
    ClickOutside,
    Resize,
    Touch
  },

  data: () => ({
    isActive: false,
    touchArea: {
      left: 0,
      right: 0
    }
  }),

  props: {
    clipped: Boolean,
    disableRouteWatcher: Boolean,
    disableResizeWatcher: Boolean,
    height: {
      type: [Number, String],
      default: '100%'
    },
    floating: Boolean,
    miniVariant: Boolean,
    miniVariantWidth: {
      type: [Number, String],
      default: 80
    },
    mobileBreakPoint: {
      type: [Number, String],
      default: 1264
    },
    permanent: Boolean,
    right: Boolean,
    stateless: Boolean,
    temporary: Boolean,
    touchless: Boolean,
    width: {
      type: [Number, String],
      default: 300
    },
    value: { required: false }
  },

  computed: {
    /**
     * Used for setting an app
     * value from a dynamic
     * property. Called from
     * applicationable.js
     *
     * @return {string}
     */
    applicationProperty () {
      return this.right ? 'right' : 'left'
    },
    calculatedHeight () {
      return isNaN(this.height) ? this.height : `${this.height}px`
    },
    calculatedTransform () {
      if (this.isActive) return 0

      return this.right
        ? this.calculatedWidth
        : -this.calculatedWidth
    },
    calculatedWidth () {
      return this.miniVariant
        ? this.miniVariantWidth
        : this.width
    },
    classes () {
      return {
        'navigation-drawer': true,
        'navigation-drawer--absolute': this.absolute,
        'navigation-drawer--clipped': this.clipped,
        'navigation-drawer--close': !this.isActive,
        'navigation-drawer--fixed': !this.absolute && (this.app || this.fixed),
        'navigation-drawer--floating': this.floating,
        'navigation-drawer--is-mobile': this.isMobile,
        'navigation-drawer--mini-variant': this.miniVariant,
        'navigation-drawer--open': this.isActive,
        'navigation-drawer--right': this.right,
        'navigation-drawer--temporary': this.temporary,
        'theme--dark': this.dark,
        'theme--light': this.light
      }
    },
    isMobile () {
      return !this.permanent &&
        !this.temporary &&
        this.$vuetify.breakpoint.width < parseInt(this.mobileBreakPoint, 10)
    },
    marginTop () {
      if (!this.app) return 0
      let marginTop = this.$vuetify.application.bar

      marginTop += this.clipped
        ? this.$vuetify.application.top
        : 0

      return marginTop
    },
    maxHeight () {
      if (!this.app) return '100%'

      return this.clipped
        ? this.$vuetify.application.top + this.$vuetify.application.bottom
        : this.$vuetify.application.bottom
    },
    reactsToClick () {
      return !this.stateless &&
        !this.permanent &&
        (this.isMobile || this.temporary)
    },
    reactsToMobile () {
      return !this.disableResizeWatcher &&
        !this.stateless &&
        !this.permanent &&
        !this.temporary
    },
    reactsToRoute () {
      return !this.disableRouteWatcher &&
        !this.stateless &&
        (this.temporary || this.isMobile)
    },
    resizeIsDisabled () {
      return this.disableResizeWatcher || this.stateless
    },
    showOverlay () {
      return this.isActive &&
        (this.temporary || this.isMobile)
    },
    styles () {
      const styles = {
        height: this.calculatedHeight,
        marginTop: `${this.marginTop}px`,
        maxHeight: `calc(100% - ${this.maxHeight}px)`,
        transform: `translateX(${this.calculatedTransform}px)`,
        width: `${this.calculatedWidth}px`
      }

      return styles
    }
  },

  watch: {
    $route () {
      if (this.reactsToRoute) {
        this.isActive = !this.closeConditional()
      }
    },
    isActive (val) {
      this.$emit('input', val)

      if (this.temporary || this.isMobile) {
        this.tryOverlay()
        this.$el && (this.$el.scrollTop = 0)
      }

      this.callUpdate()
    },
    /**
     * When mobile changes, adjust
     * the active state only when
     * there has been a previous
     * value
     */
    isMobile (val, prev) {
      !val &&
        this.isActive &&
        !this.temporary &&
        this.removeOverlay()

      if (prev == null ||
        this.resizeIsDisabled ||
        !this.reactsToMobile
      ) return

      this.isActive = !val
      this.callUpdate()
    },
    permanent (val) {
      // If enabling prop
      // enable the drawer
      if (val) {
        this.isActive = true
      }
      this.callUpdate()
    },
    temporary () {
      this.tryOverlay()
      this.callUpdate()
    },
    value (val) {
      if (this.permanent) return

      if (val !== this.isActive) this.isActive = val
    }
  },

  beforeMount () {
    this.init()
  },

  methods: {
    calculateTouchArea () {
      if (!this.$el.parentNode) return
      const parentRect = this.$el.parentNode.getBoundingClientRect()

      this.touchArea = {
        left: parentRect.left + 50,
        right: parentRect.right - 50
      }
    },
    closeConditional () {
      return this.reactsToClick
    },
    genDirectives () {
      const directives = [
        { name: 'click-outside', value: this.closeConditional }
      ]

      !this.touchless && directives.push({
        name: 'touch',
        value: {
          parent: true,
          left: this.swipeLeft,
          right: this.swipeRight
        }
      })

      return directives
    },
    /**
     * Sets state before mount to avoid
     * entry transitions in SSR
     *
     * @return {void}
     */
    init () {
      if (this.permanent) {
        this.isActive = true
      } else if (this.stateless ||
        this.value != null
      ) {
        this.isActive = this.value
      } else if (!this.temporary) {
        this.isActive = !this.isMobile
      }
    },
    swipeRight (e) {
      if (this.isActive && !this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      else if (!this.right &&
        e.touchstartX <= this.touchArea.left
      ) this.isActive = true
      else if (this.right && this.isActive) this.isActive = false
    },
    swipeLeft (e) {
      if (this.isActive && this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      else if (this.right &&
        e.touchstartX >= this.touchArea.right
      ) this.isActive = true
      else if (!this.right && this.isActive) this.isActive = false
    },
    tryOverlay () {
      if (!this.permanent &&
        this.showOverlay &&
        this.isActive
      ) {
        return this.genOverlay()
      }

      this.removeOverlay()
    },
    /**
     * Update the application layout
     *
     * @return {number}
     */
    updateApplication () {
      return !this.isActive ||
        this.temporary ||
        this.isMobile
        ? 0
        : this.calculatedWidth
    }
  },

  render (h) {
    const data = {
      'class': this.classes,
      style: this.styles,
      directives: this.genDirectives(),
      on: {
        click: () => {
          if (!this.miniVariant) return

          this.$emit('update:miniVariant', false)
        }
      }
    }

    return h('aside', data, [
      this.$slots.default,
      h('div', { 'class': 'navigation-drawer__border' })
    ])
  }
}
<commit_msg>enh(VNavigationDrawer): re-initialise value when set to null<commit_after>require('../../stylus/components/_navigation-drawer.styl')

// Mixins
import Applicationable from '../../mixins/applicationable'
import Overlayable from '../../mixins/overlayable'
import SSRBootable from '../../mixins/ssr-bootable'
import Themeable from '../../mixins/themeable'

// Directives
import ClickOutside from '../../directives/click-outside'
import Resize from '../../directives/resize'
import Touch from '../../directives/touch'

export default {
  name: 'v-navigation-drawer',

  mixins: [
    Applicationable(null, [
      'miniVariant',
      'right',
      'width'
    ]),
    Overlayable,
    SSRBootable,
    Themeable
  ],

  directives: {
    ClickOutside,
    Resize,
    Touch
  },

  data: () => ({
    isActive: false,
    touchArea: {
      left: 0,
      right: 0
    }
  }),

  props: {
    clipped: Boolean,
    disableRouteWatcher: Boolean,
    disableResizeWatcher: Boolean,
    height: {
      type: [Number, String],
      default: '100%'
    },
    floating: Boolean,
    miniVariant: Boolean,
    miniVariantWidth: {
      type: [Number, String],
      default: 80
    },
    mobileBreakPoint: {
      type: [Number, String],
      default: 1264
    },
    permanent: Boolean,
    right: Boolean,
    stateless: Boolean,
    temporary: Boolean,
    touchless: Boolean,
    width: {
      type: [Number, String],
      default: 300
    },
    value: { required: false }
  },

  computed: {
    /**
     * Used for setting an app
     * value from a dynamic
     * property. Called from
     * applicationable.js
     *
     * @return {string}
     */
    applicationProperty () {
      return this.right ? 'right' : 'left'
    },
    calculatedHeight () {
      return isNaN(this.height) ? this.height : `${this.height}px`
    },
    calculatedTransform () {
      if (this.isActive) return 0

      return this.right
        ? this.calculatedWidth
        : -this.calculatedWidth
    },
    calculatedWidth () {
      return this.miniVariant
        ? this.miniVariantWidth
        : this.width
    },
    classes () {
      return {
        'navigation-drawer': true,
        'navigation-drawer--absolute': this.absolute,
        'navigation-drawer--clipped': this.clipped,
        'navigation-drawer--close': !this.isActive,
        'navigation-drawer--fixed': !this.absolute && (this.app || this.fixed),
        'navigation-drawer--floating': this.floating,
        'navigation-drawer--is-mobile': this.isMobile,
        'navigation-drawer--mini-variant': this.miniVariant,
        'navigation-drawer--open': this.isActive,
        'navigation-drawer--right': this.right,
        'navigation-drawer--temporary': this.temporary,
        'theme--dark': this.dark,
        'theme--light': this.light
      }
    },
    isMobile () {
      return !this.permanent &&
        !this.temporary &&
        this.$vuetify.breakpoint.width < parseInt(this.mobileBreakPoint, 10)
    },
    marginTop () {
      if (!this.app) return 0
      let marginTop = this.$vuetify.application.bar

      marginTop += this.clipped
        ? this.$vuetify.application.top
        : 0

      return marginTop
    },
    maxHeight () {
      if (!this.app) return '100%'

      return this.clipped
        ? this.$vuetify.application.top + this.$vuetify.application.bottom
        : this.$vuetify.application.bottom
    },
    reactsToClick () {
      return !this.stateless &&
        !this.permanent &&
        (this.isMobile || this.temporary)
    },
    reactsToMobile () {
      return !this.disableResizeWatcher &&
        !this.stateless &&
        !this.permanent &&
        !this.temporary
    },
    reactsToRoute () {
      return !this.disableRouteWatcher &&
        !this.stateless &&
        (this.temporary || this.isMobile)
    },
    resizeIsDisabled () {
      return this.disableResizeWatcher || this.stateless
    },
    showOverlay () {
      return this.isActive &&
        (this.temporary || this.isMobile)
    },
    styles () {
      const styles = {
        height: this.calculatedHeight,
        marginTop: `${this.marginTop}px`,
        maxHeight: `calc(100% - ${this.maxHeight}px)`,
        transform: `translateX(${this.calculatedTransform}px)`,
        width: `${this.calculatedWidth}px`
      }

      return styles
    }
  },

  watch: {
    $route () {
      if (this.reactsToRoute) {
        this.isActive = !this.closeConditional()
      }
    },
    isActive (val) {
      this.$emit('input', val)

      if (this.temporary || this.isMobile) {
        this.tryOverlay()
        this.$el && (this.$el.scrollTop = 0)
      }

      this.callUpdate()
    },
    /**
     * When mobile changes, adjust
     * the active state only when
     * there has been a previous
     * value
     */
    isMobile (val, prev) {
      !val &&
        this.isActive &&
        !this.temporary &&
        this.removeOverlay()

      if (prev == null ||
        this.resizeIsDisabled ||
        !this.reactsToMobile
      ) return

      this.isActive = !val
      this.callUpdate()
    },
    permanent (val) {
      // If enabling prop
      // enable the drawer
      if (val) {
        this.isActive = true
      }
      this.callUpdate()
    },
    temporary () {
      this.tryOverlay()
      this.callUpdate()
    },
    value (val) {
      if (this.permanent) return

      if (val == null) return this.init()

      if (val !== this.isActive) this.isActive = val
    }
  },

  beforeMount () {
    this.init()
  },

  methods: {
    calculateTouchArea () {
      if (!this.$el.parentNode) return
      const parentRect = this.$el.parentNode.getBoundingClientRect()

      this.touchArea = {
        left: parentRect.left + 50,
        right: parentRect.right - 50
      }
    },
    closeConditional () {
      return this.reactsToClick
    },
    genDirectives () {
      const directives = [
        { name: 'click-outside', value: this.closeConditional }
      ]

      !this.touchless && directives.push({
        name: 'touch',
        value: {
          parent: true,
          left: this.swipeLeft,
          right: this.swipeRight
        }
      })

      return directives
    },
    /**
     * Sets state before mount to avoid
     * entry transitions in SSR
     *
     * @return {void}
     */
    init () {
      if (this.permanent) {
        this.isActive = true
      } else if (this.stateless ||
        this.value != null
      ) {
        this.isActive = this.value
      } else if (!this.temporary) {
        this.isActive = !this.isMobile
      }
    },
    swipeRight (e) {
      if (this.isActive && !this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      else if (!this.right &&
        e.touchstartX <= this.touchArea.left
      ) this.isActive = true
      else if (this.right && this.isActive) this.isActive = false
    },
    swipeLeft (e) {
      if (this.isActive && this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      else if (this.right &&
        e.touchstartX >= this.touchArea.right
      ) this.isActive = true
      else if (!this.right && this.isActive) this.isActive = false
    },
    tryOverlay () {
      if (!this.permanent &&
        this.showOverlay &&
        this.isActive
      ) {
        return this.genOverlay()
      }

      this.removeOverlay()
    },
    /**
     * Update the application layout
     *
     * @return {number}
     */
    updateApplication () {
      return !this.isActive ||
        this.temporary ||
        this.isMobile
        ? 0
        : this.calculatedWidth
    }
  },

  render (h) {
    const data = {
      'class': this.classes,
      style: this.styles,
      directives: this.genDirectives(),
      on: {
        click: () => {
          if (!this.miniVariant) return

          this.$emit('update:miniVariant', false)
        }
      }
    }

    return h('aside', data, [
      this.$slots.default,
      h('div', { 'class': 'navigation-drawer__border' })
    ])
  }
}
<|endoftext|>


<commit_before>import test from 'tape'
import fabricComponent from '../index.js'

test('fabricComponent as a decorator', t => {
  @fabricComponent
  class Foo { }

  t.equal(Foo.displayName, 'FabricComponent(Foo)')
  t.end()
})

test('fabricComponent as a higher order function', t => {
  class Foo { }

  t.equal(fabricComponent(Foo).WrappedComponent, Foo)
  t.end()
})
<commit_msg>test(fabricComponent): configure() test<commit_after>import test from 'tape'
import fabricComponent, { configure } from '../index.js'

test('fabricComponent as a decorator', t => {
  @fabricComponent
  class Foo { }

  t.equal(Foo.displayName, 'FabricComponent(Foo)')
  t.end()
})

test('fabricComponent as a higher order function', t => {
  class Foo { }

  t.equal(fabricComponent(Foo).WrappedComponent, Foo)
  t.end()
})

test('fabricComponent#configure', t => {
  t.doesNotThrow(() => configure({}))

  t.end()
})
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'MinMaxCapabilityWizardlet',
  extends: 'foam.nanos.crunch.ui.CapabilityWizardlet',
  implements: [ 'foam.nanos.crunch.ui.PrerequisiteAwareWizardlet' ],

  requires: [
    'foam.u2.view.MultiChoiceView',
    'foam.u2.view.CardSelectView',
    'foam.nanos.crunch.CapabilityJunctionStatus'
  ],

  imports: [
    'translationService'
  ],

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.u2.wizard.Wizardlet',
      name: 'choiceWizardlets',
      factory: function() {
        return [];
      }
    },
    {
      name: 'min',
      class: 'Int',
      factory: function(){
        if ( foam.nanos.crunch.MinMaxCapability.isInstance(this.capability) ){
          // a capability min of 0 denotes no minimum limit
          return this.capability.min > 0 ? this.capability.min : this.choices.length;
        }
      }
    },
    {
      name: 'max',
      class: 'Int',
      factory: function(){
        if ( foam.nanos.crunch.MinMaxCapability.isInstance(this.capability) ){
          return this.capability.max;
        }
      }
    },
    {
      name: 'choices',
      expression: function(choiceWizardlets){
        var self = this;
        return choiceWizardlets.map(wizardlet => {
          var isFinal =
            wizardlet.status === this.CapabilityJunctionStatus.GRANTED ||
            wizardlet.status === this.CapabilityJunctionStatus.PENDING;

          return [wizardlet.title, self.translationService.getTranslation(foam.locale, `${wizardlet.capability.id}.name`,wizardlet.title), isFinal ? true : wizardlet.isAvailable$, isFinal ?  foam.u2.DisplayMode.DISABLED : foam.u2.DisplayMode.RW, isFinal]
        })
      }
    },
    {
      class: 'Boolean',
      name: 'isValid',
      value: false
    },
    {
      class: 'Boolean',
      name: 'isVisible',
      expression: function (isAvailable) {
        return isAvailable;
      }
    },
    {
      name: 'isAvailable',
      class: 'Boolean',
      value: true,
      documentation: `
        Specify the availability of this wizardlet. If true, wizardlet is
        available iff at least one section is available. If false, wizardlet
        does not display even if some sections are available.
      `,
      postSet: function(_,n){
        if ( !n ){
          this.choiceWizardlets.forEach(cw => {
            cw.isAvailable = false
            cw.cancel();
          });

          this.cancel();
        } else {
          this.save();
        }
      }
    },
    {
      name: 'sections',
      flags: ['web'],
      transient: true,
      class: 'FObjectArray',
      of: 'foam.u2.wizard.WizardletSection',
      factory: function () {
        return [
          this.WizardletSection.create({
            isAvailable: true,
            title: this.capability.name,
            customView: {
              class: 'foam.u2.view.MultiChoiceView',
              choices$: this.choices$,
              booleanView: this.CardSelectView, // TODO
              isValidNumberOfChoices$: this.isValid$,
              showValidNumberOfChoicesHelper: false,
              minSelected$: this.min$,
              maxSelected$: this.max$
            }
          })
        ];
      }
    }
  ],

  methods: [
    function createView(data) {
      return this.MultiChoiceView.create({
        choices$: this.choices$,
        booleanView: this.CardSelectView,
        isValidNumberOfChoices$: this.isValid$,
        minSelected$: this.min$,
        maxSelected$: this.max$
      });
    },
    function addPrerequisite(wizardlet) {
      wizardlet.isAvailable = false;
      this.choiceWizardlets.push(wizardlet);
    }
  ]
});
<commit_msg>reverting todo in minmax<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch.ui',
  name: 'MinMaxCapabilityWizardlet',
  extends: 'foam.nanos.crunch.ui.CapabilityWizardlet',
  implements: [ 'foam.nanos.crunch.ui.PrerequisiteAwareWizardlet' ],

  requires: [
    'foam.u2.view.MultiChoiceView',
    'foam.u2.view.CardSelectView',
    'foam.nanos.crunch.CapabilityJunctionStatus'
  ],

  imports: [
    'translationService'
  ],

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.u2.wizard.Wizardlet',
      name: 'choiceWizardlets',
      factory: function() {
        return [];
      }
    },
    {
      name: 'min',
      class: 'Int',
      factory: function(){
        if ( foam.nanos.crunch.MinMaxCapability.isInstance(this.capability) ){
          // a capability min of 0 denotes no minimum limit
          return this.capability.min > 0 ? this.capability.min : this.choices.length;
        }
      }
    },
    {
      name: 'max',
      class: 'Int',
      factory: function(){
        if ( foam.nanos.crunch.MinMaxCapability.isInstance(this.capability) ){
          return this.capability.max;
        }
      }
    },
    {
      name: 'choices',
      expression: function(choiceWizardlets){
        var self = this;
        return choiceWizardlets.map(wizardlet => {
          var isFinal =
            wizardlet.status === this.CapabilityJunctionStatus.GRANTED ||
            wizardlet.status === this.CapabilityJunctionStatus.PENDING;

          return [wizardlet.title, self.translationService.getTranslation(foam.locale, `${wizardlet.capability.id}.name`,wizardlet.title), isFinal ? true : wizardlet.isAvailable$, isFinal ?  foam.u2.DisplayMode.DISABLED : foam.u2.DisplayMode.RW, isFinal]
        })
      }
    },
    {
      class: 'Boolean',
      name: 'isValid',
      value: false
    },
    {
      class: 'Boolean',
      name: 'isVisible',
      expression: function (isAvailable) {
        return isAvailable;
      }
    },
    {
      name: 'isAvailable',
      class: 'Boolean',
      value: true,
      documentation: `
        Specify the availability of this wizardlet. If true, wizardlet is
        available iff at least one section is available. If false, wizardlet
        does not display even if some sections are available.
      `,
      postSet: function(_,n){
        if ( !n ){
          this.choiceWizardlets.forEach(cw => {
            cw.isAvailable = false
            cw.cancel();
          });

          this.cancel();
        } else {
          this.save();
        }
      }
    },
    {
      name: 'sections',
      flags: ['web'],
      transient: true,
      class: 'FObjectArray',
      of: 'foam.u2.wizard.WizardletSection',
      factory: function () {
        return [
          this.WizardletSection.create({
            isAvailable: true,
            title: this.capability.name,
            customView: {
              class: 'foam.u2.view.MultiChoiceView',
              choices$: this.choices$,
              booleanView: this.CardSelectView,
              isValidNumberOfChoices$: this.isValid$,
              showValidNumberOfChoicesHelper: false,
              minSelected$: this.min$,
              maxSelected$: this.max$
            }
          })
        ];
      }
    }
  ],

  methods: [
    function createView(data) {
      return this.MultiChoiceView.create({
        choices$: this.choices$,
        booleanView: this.CardSelectView,
        isValidNumberOfChoices$: this.isValid$,
        minSelected$: this.min$,
        maxSelected$: this.max$
      });
    },
    function addPrerequisite(wizardlet) {
      wizardlet.isAvailable = false;
      this.choiceWizardlets.push(wizardlet);
    }
  ]
});
<|endoftext|>


<commit_before>import Posts from "meteor/nova:posts";
import Users from 'meteor/nova:users';
import Categories from 'meteor/nova:categories';

/**
 * @summary Verify that the un/subscription can be performed
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user: current user (xxx: legacy, to replace with this.userId)
 * @returns {Object} collectionName, fields: object, item, hasSubscribedItem: boolean
 */
const prepareSubscription = (action, collection, itemId, user) => {

  // get item's collection name
  const collectionName = collection._name.slice(0,1).toUpperCase() + collection._name.slice(1);

  // get item data
  const item = collection.findOne(itemId);

  // there no user logged in or no item, abort process
  if (!user || !item) {
    return false;
  }

  // edge case: Users collection
  if (collectionName === 'Users') {
    // someone can't subscribe to themself, abort process
    if (item._id === user._id) {
      return false;
    }
  } else {
    // the item's owner is the subscriber, abort process
    if (item.userId && item.userId === user._id) {
      return false;
    }
  }

  // assign the right fields depending on the collection
  const fields = {
    subscribers: collectionName === 'Users' ? 'telescope.subscribers' : 'subscribers',
    subscriberCount: collectionName === 'Users' ? 'telescope.subscriberCount' : 'subscriberCount',
  };

  // return true if the item has the subscriber's id in its fields
  const hasSubscribedItem = !!_.deep(item, fields.subscribers) && _.deep(item, fields.subscribers) && _.deep(item, fields.subscribers).indexOf(user._id) !== -1;

  // assign the right update operator and count depending on the action type
  const updateQuery = action === 'subscribe' ? {
    findOperator: '$ne', // where 'IT' isn't...
    updateOperator: '$addToSet', // ...add 'IT' to the array...
    updateCount: 1, // ...and log the addition +1
  } : {
    findOperator: '$eq', // where 'IT' is...
    updateOperator: '$pull', // ...remove 'IT' from the array...
    updateCount: -1, // ...and log the subtraction -1
  };

  // return the utility object to pursue
  return {
    collectionName,
    fields,
    item,
    hasSubscribedItem,
    ...updateQuery,
  };
}

/**
 * @summary Perform the un/subscription after verification: update the collection item & the user
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user: current user (xxx: legacy, to replace with this.userId)
 * @returns {Boolean}
 */
const performSubscriptionAction = (action, collection, itemId, user) => {

  // subscription preparation to verify if can pursue and give shorthand variables
  const subscription = prepareSubscription(action, collection, itemId, user);

  // Abort process if the situation matches one of these cases:
  // - subscription preparation failed (ex: no user, no item, subscriber is author's item, ... see all cases above)
  // - the action is subscribe but the user has already subscribed to this item
  // - the action is unsubscribe but the user hasn't subscribed to this item
  if (!subscription || (action === 'subscribe' && subscription.hasSubscribedItem) || (action === 'unsubscribe' && !subscription.hasSubscribedItem)) {
    return false; // xxx: should return exploitable error
  }

  // shorthand for useful variables
  const { collectionName, fields, item, findOperator, updateOperator, updateCount } = subscription;

  // Perform the action, eg. operate on the item's collection
  const result = collection.update({
    _id: itemId,
    // if it's a subscription, find  where there are not the user (ie. findOperator = $ne), else it will be $in
    [fields.subscribers]: { [findOperator]: user._id }
  }, {
    // if it's a subscription, add a subscriber (ie. updateOperator = $addToSet), else it will be $pull
    [updateOperator]: { [fields.subscribers]: user._id },
    // if it's a subscription, the count is incremented of 1, else decremented of 1
    $inc: { [fields.subscriberCount]: updateCount },
  });

  // log the operation on the subscriber if it has succeeded
  if (result > 0) {
    // id of the item subject of the action
    let loggedItem = {
      itemId: item._id,
    };

    // in case of subscription, log also the date
    if (action === 'subscribe') {
      loggedItem = {
        ...loggedItem,
        subscribedAt: new Date()
      };
    }

    // update the user's list of subscribed items
    Users.update({
      _id: user._id
    }, {
      [updateOperator]: { [`telescope.subscribedItems.${collectionName}`]: loggedItem }
    });

    return true; // action completed! ✅
  } else {
    return false; // xxx: should return exploitable error
  }
};

// collections = array of name ['posts', 'users', categories]
const subscribeMethodsGenerator = (collections) => {

  const genericMethodFunction = (collection, action) => {
    // get the lower case collection name

    return function(docId, userId) {
      check(docId, String);
      check(userId, Match.Maybe(String));

      const currentUser = Users.findOne({_id: this.userId});
      const user = typeof userId !== "undefined" ? Users.findOne({_id: userId }) : currentUser;

      if (!Users.canDo(currentUser, `${collection._name}.${action}`) || typeof userId !== "undefined" && !Users.canDo(currentUser, `${collection._name}.${action}.all`)) {
        throw new Meteor.Error(601, "You don't have the permission to do this");
      }

      return performSubscriptionAction(action, collection, docId, user);
    };
  };

  //
  return collections
          .map(collection => {
            const collectionName = collection._name;
            return {
              [`${collectionName}.subscribe`]: genericMethodFunction(collection, 'subscribe'),
              [`${collectionName}.unsubscribe`]: genericMethodFunction(collection, 'unsubscribe')
            };
          })
          .reduce((methods, couple) => ({
            ...methods,
            ...couple,
          }), {});
};

Meteor.methods(subscribeMethodsGenerator([Posts, Users, Categories]));
<commit_msg>clarify some things 🤓<commit_after>import Posts from "meteor/nova:posts";
import Users from 'meteor/nova:users';
import Categories from 'meteor/nova:categories';

/**
 * @summary Verify that the un/subscription can be performed
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user
 * @returns {Object} collectionName, fields: object, item, hasSubscribedItem: boolean
 */
const prepareSubscription = (action, collection, itemId, user) => {

  // get item's collection name
  const collectionName = collection._name.slice(0,1).toUpperCase() + collection._name.slice(1);

  // get item data
  const item = collection.findOne(itemId);

  // there no user logged in or no item, abort process
  if (!user || !item) {
    return false;
  }

  // edge case: Users collection
  if (collectionName === 'Users') {
    // someone can't subscribe to themself, abort process
    if (item._id === user._id) {
      return false;
    }
  } else {
    // the item's owner is the subscriber, abort process
    if (item.userId && item.userId === user._id) {
      return false;
    }
  }

  // assign the right fields depending on the collection
  const fields = {
    subscribers: collectionName === 'Users' ? 'telescope.subscribers' : 'subscribers',
    subscriberCount: collectionName === 'Users' ? 'telescope.subscriberCount' : 'subscriberCount',
  };

  // return true if the item has the subscriber's id in its fields
  const hasSubscribedItem = !!_.deep(item, fields.subscribers) && _.deep(item, fields.subscribers) && _.deep(item, fields.subscribers).indexOf(user._id) !== -1;

  // assign the right update operator and count depending on the action type
  const updateQuery = action === 'subscribe' ? {
    findOperator: '$ne', // where 'IT' isn't...
    updateOperator: '$addToSet', // ...add 'IT' to the array...
    updateCount: 1, // ...and log the addition +1
  } : {
    findOperator: '$eq', // where 'IT' is...
    updateOperator: '$pull', // ...remove 'IT' from the array...
    updateCount: -1, // ...and log the subtraction -1
  };

  // return the utility object to pursue
  return {
    collectionName,
    fields,
    item,
    hasSubscribedItem,
    ...updateQuery,
  };
};

/**
 * @summary Perform the un/subscription after verification: update the collection item & the user
 * @param {String} action
 * @param {Collection} collection
 * @param {String} itemId
 * @param {Object} user: current user (xxx: legacy, to replace with this.userId)
 * @returns {Boolean}
 */
const performSubscriptionAction = (action, collection, itemId, user) => {

  // subscription preparation to verify if can pursue and give shorthand variables
  const subscription = prepareSubscription(action, collection, itemId, user);

  // Abort process if the situation matches one of these cases:
  // - subscription preparation failed (ex: no user, no item, subscriber is author's item, ... see all cases above)
  // - the action is subscribe but the user has already subscribed to this item
  // - the action is unsubscribe but the user hasn't subscribed to this item
  if (!subscription || (action === 'subscribe' && subscription.hasSubscribedItem) || (action === 'unsubscribe' && !subscription.hasSubscribedItem)) {
    return false; // xxx: should return exploitable error
  }

  // shorthand for useful variables
  const { collectionName, fields, item, findOperator, updateOperator, updateCount } = subscription;

  // Perform the action, eg. operate on the item's collection
  const result = collection.update({
    _id: itemId,
    // if it's a subscription, find  where there are not the user (ie. findOperator = $ne), else it will be $in
    [fields.subscribers]: { [findOperator]: user._id }
  }, {
    // if it's a subscription, add a subscriber (ie. updateOperator = $addToSet), else it will be $pull
    [updateOperator]: { [fields.subscribers]: user._id },
    // if it's a subscription, the count is incremented of 1, else decremented of 1
    $inc: { [fields.subscriberCount]: updateCount },
  });

  // log the operation on the subscriber if it has succeeded
  if (result > 0) {
    // id of the item subject of the action
    let loggedItem = {
      itemId: item._id,
    };

    // in case of subscription, log also the date
    if (action === 'subscribe') {
      loggedItem = {
        ...loggedItem,
        subscribedAt: new Date()
      };
    }

    // update the user's list of subscribed items
    Users.update({
      _id: user._id
    }, {
      [updateOperator]: { [`telescope.subscribedItems.${collectionName}`]: loggedItem }
    });

    return true; // action completed! ✅
  } else {
    return false; // xxx: should return exploitable error
  }
};

/**
 * @summary Generate methods 'collection.subscribe' & 'collection.unsubscribe' automatically
 * @params {Array[Collections]} collections
 */
const subscribeMethodsGenerator = (collections) => {

  // generic method function calling the performSubscriptionAction
  const genericMethodFunction = (collection, action) => {
    // return the method code
    return function(docId, userId) {
      check(docId, String);
      check(userId, Match.Maybe(String));

      const currentUser = Users.findOne({_id: this.userId}); // this refers to Meteor thanks to previous fat arrows when this function-builder is used
      const user = typeof userId !== "undefined" ? Users.findOne({_id: userId }) : currentUser;

      if (!Users.canDo(currentUser, `${collection._name}.${action}`) || typeof userId !== "undefined" && !Users.canDo(currentUser, `${collection._name}.${action}.all`)) {
        throw new Meteor.Error(601, "You don't have the permission to do this");
      }

      return performSubscriptionAction(action, collection, docId, user);
    };
  };
  // return an object of the shape expected by Meteor.methods
  return collections
          // map over the collection and create the methods associated
          .map(collection => {
            const collectionName = collection._name;
            return {
              [`${collectionName}.subscribe`]: genericMethodFunction(collection, 'subscribe'),
              [`${collectionName}.unsubscribe`]: genericMethodFunction(collection, 'unsubscribe')
            };
          })
          // reduce this array in an object of methods
          .reduce((methods, couple) => ({
            ...methods,
            ...couple,
          }), {});
};

// Finally. Add the methods to the Meteor namespace 🖖
Meteor.methods(subscribeMethodsGenerator([Posts, Users, Categories]));
<|endoftext|>


<commit_before>/** @license MIT License (c) copyright 2016 original author or authors */

import {describe, it} from 'mocha';
import assert from 'assert';

import { id } from '../src/function';

describe('id', () => {
  it('should return its input', () => {
    const x = {};
    assert(id(x) === x);
  });
});
<commit_msg>Add unit tests for compose and apply<commit_after>/** @license MIT License (c) copyright 2016 original author or authors */

import {describe, it} from 'mocha';
import assert from 'assert';

import { id, compose, apply } from '../src/function';

describe('id', () => {
  it('id(x) === x', () => {
    const x = {};
    assert.strictEqual(id(x), x);
  });
});

describe('compose', () => {
  it('compose(f, g)(x) === f(g(x))', () => {
    const fx = '' + Math.random();
    const gx = '' + Math.random();
    const x = '' + Math.random();

    const f = x => x + fx;
    const g = x => x + gx;
    const h = compose(f, g);

    assert.strictEqual(f(g(x)), h(x));
  });
});

describe('apply', () => {
  it('apply(f, x) === f(x)', () => {
    const x = Math.random();
    const f = x => x+1;
    assert.strictEqual(apply(f, x), f(x));
  });
});
<|endoftext|>


<commit_before>import { globalObject } from "./globalObject.js";

var atob, btoa;

(function() {
  // @if MODULE_FORMAT!='cjs'
  atob = globalObject.atob;
  btoa = globalObject.btoa;
  return;
  // @endif

  // @if MODULE_FORMAT='cjs'
  atob = require("atob");
  btoa = require("btoa");
  // @endif
})();

export { atob, btoa };
<commit_msg>fix atob/btoa for IE/old Edge<commit_after>import { globalObject } from "./globalObject.js";

var atob, btoa;

(function() {
  // @if MODULE_FORMAT!='cjs'
  atob = globalObject.atob.bind(globalObject);
  btoa = globalObject.btoa.bind(globalObject);
  return;
  // @endif

  // @if MODULE_FORMAT='cjs'
  atob = require("atob");
  btoa = require("btoa");
  // @endif
})();

export { atob, btoa };
<|endoftext|>


<commit_before>var $module=(function($B) {
  return {
    alert:function(message){window.alert($B.builtins.str(message))},
    confirm: function(message){return $B.JSObject(window.confirm(message))},
    console:{log:function(data){window.console.log(data)}},
    document:$B.$DOMNode(document),
    doc: $B.$DOMNode(document),   //want to use document instead of doc
    DOMEvent:$B.DOMEvent,
    DOMNode:$B.DOMNode,
    mouseCoords: function(ev){return $B.JSObject($mouseCoords(ev))},
    prompt: function(message, default_value){
        return $B.JSObject(window.prompt(message, default_value||''))
    },
    win: $B.win,
    window: $B.win,
    URLParameter:function(name) {
       name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
       var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
           results = regex.exec(location.search);
       results= results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
       return $B.builtins.str(results);
    }
  }
})(__BRYTHON__)
<commit_msg>Better implementation of browser.console<commit_after>var $module=(function($B) {
  return {
    alert:function(message){window.alert($B.builtins.str(message))},
    confirm: $B.JSObject(window.confirm),
    console:$B.JSObject(window.console),
    document:$B.$DOMNode(document),
    doc: $B.$DOMNode(document),   //want to use document instead of doc
    DOMEvent:$B.DOMEvent,
    DOMNode:$B.DOMNode,
    mouseCoords: function(ev){return $B.JSObject($mouseCoords(ev))},
    prompt: function(message, default_value){
        return $B.JSObject(window.prompt(message, default_value||''))
    },
    win: $B.win,
    window: $B.win,
    URLParameter:function(name) {
       name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
       var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
           results = regex.exec(location.search);
       results= results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
       return $B.builtins.str(results);
    }
  }
})(__BRYTHON__)
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Utils = require('../../renderer/webgl/Utils');

/**
 * Renders this Game Object with the WebGL Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.TileSprite#renderWebGL
 * @since 3.0.0
 * @private
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
 * @param {Phaser.GameObjects.TileSprite} src - The Game Object being rendered in this call.
 * @param {number} interpolationPercentage - Reserved for future use and custom pipelines.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var TileSpriteWebGLRenderer = function (renderer, src, interpolationPercentage, camera, parentMatrix)
{
    src.updateCanvas();

    var getTint = Utils.getTintAppendFloatAlpha;

    this.pipeline.batchTexture(
        src,
        src.fillPattern,
        src.displayFrame.width * src.tileScaleX, src.displayFrame.height * src.tileScaleY,
        src.x, src.y,
        src.width, src.height,
        src.scaleX, src.scaleY,
        src.rotation,
        src.flipX, src.flipY,
        src.scrollFactorX, src.scrollFactorY,
        src.originX * src.width, src.originY * src.height,
        0, 0, src.width, src.height,
        getTint(src._tintTL, camera.alpha * src._alphaTL),
        getTint(src._tintTR, camera.alpha * src._alphaTR),
        getTint(src._tintBL, camera.alpha * src._alphaBL),
        getTint(src._tintBR, camera.alpha * src._alphaBR),
        (src._isTinted && src.tintFill),
        (src.tilePositionX % src.displayFrame.width) / src.displayFrame.width,
        (src.tilePositionY % src.displayFrame.height) / src.displayFrame.height,
        camera,
        parentMatrix
    );
};

module.exports = TileSpriteWebGLRenderer;
<commit_msg>The TileSprite Game Objects now support rendering with normal maps.<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2020 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

var Utils = require('../../renderer/webgl/Utils');

/**
 * Renders this Game Object with the WebGL Renderer to the given Camera.
 * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera.
 * This method should not be called directly. It is a utility function of the Render module.
 *
 * @method Phaser.GameObjects.TileSprite#renderWebGL
 * @since 3.0.0
 * @private
 *
 * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - A reference to the current active WebGL renderer.
 * @param {Phaser.GameObjects.TileSprite} src - The Game Object being rendered in this call.
 * @param {number} interpolationPercentage - Reserved for future use and custom pipelines.
 * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera that is rendering the Game Object.
 * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - This transform matrix is defined if the game object is nested
 */
var TileSpriteWebGLRenderer = function (renderer, src, interpolationPercentage, camera, parentMatrix)
{
    src.updateCanvas();

    var width = src.width;
    var height = src.height;
    var getTint = Utils.getTintAppendFloatAlpha;
    var pipeline = this.pipeline;

    if (width === 0 || height === 0)
    {
        return;
    }

    renderer.setPipeline(pipeline, src);

    var textureUnit = pipeline.setTexture2D(src.fillPattern, src);

    pipeline.batchTexture(
        src,
        src.fillPattern,
        src.displayFrame.width * src.tileScaleX, src.displayFrame.height * src.tileScaleY,
        src.x, src.y,
        width, height,
        src.scaleX, src.scaleY,
        src.rotation,
        src.flipX, src.flipY,
        src.scrollFactorX, src.scrollFactorY,
        src.originX * width, src.originY * height,
        0, 0, width, height,
        getTint(src._tintTL, camera.alpha * src._alphaTL),
        getTint(src._tintTR, camera.alpha * src._alphaTR),
        getTint(src._tintBL, camera.alpha * src._alphaBL),
        getTint(src._tintBR, camera.alpha * src._alphaBR),
        (src._isTinted && src.tintFill),
        (src.tilePositionX % src.displayFrame.width) / src.displayFrame.width,
        (src.tilePositionY % src.displayFrame.height) / src.displayFrame.height,
        camera,
        parentMatrix,
        false,
        textureUnit
    );
};

module.exports = TileSpriteWebGLRenderer;
<|endoftext|>


<commit_before>// ==UserScript==
// @name       GitHub HD Resolution
// @namespace
// @version    2016-05-02
// @description  Resize main container of GitHub to take advantage of HD Resolution Screen.
// @match      https://github.com/*
// @copyright  2014+, Omnimed.com
// @require        https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// ==/UserScript==

var $ = unsafeWindow.jQuery;

$('.container').css('width', '95%');
$('#js-repo-pjax-container').css('width', '95%');
$('.js-quote-selection-container').css('width', '87%');

$('.repository-with-sidebar .repository-sidebar').css({
    'float': 'right',
    'width': '38px',
    'overflow': 'hidden'
});

$('.repository-with-sidebar .repository-sidebar span:not(.octicon, .url-box-clippy)').css('display', 'none');
//$('.repository-content, .context-loader-container').css('width', '97%');
$('#files .diffstat+.css-truncate-target').removeClass('css-truncate css-truncate-target');
$('span.css-truncate-target').removeClass('css-truncate css-truncate-target');
$('.container .discussion-timeline').removeClass('discussion-timeline');
$('.repo-label').remove();

$(document).bind('DOMSubtreeModified', function() {
    $('.container').css('width', '95%');
	$('#files .diffstat+.css-truncate-target').removeClass('css-truncate css-truncate-target');
	$('span.css-truncate-target').removeClass('css-truncate css-truncate-target');
	$('.container .discussion-timeline').removeClass('discussion-timeline');
});
<commit_msg>Delete github-hd.js<commit_after><|endoftext|>