<commit_before>(function() {

// Localize jQuery variable
var jQuery

var baseURL = "http://oghma.herokuapp.com";

if(window.location.hostname == "localhost")
    baseURL = "http://localhost:8888";

/******** Load jQuery if not present *********/
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '2.1.1') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src",
        "//code.jquery.com/jquery-2.1.1.min.js");
    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { // For old versions of IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') {
              scriptLoadHandler();
          }
      };
    } else { // Other browsers
      script_tag.onload = scriptLoadHandler;
    }
    // Try to find the head, otherwise default to the documentElement
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
    // The jQuery version on the window is the one we want to use
    jQuery = window.jQuery;
    main();
}

/******** Called once jQuery has loaded ******/
function scriptLoadHandler() {
    // Restore $ and window.jQuery to their previous values and store the
    // new jQuery in our local jQuery variable
    jQuery = window.jQuery.noConflict(true);
    // Call our main function
    main();
}

/******** Our main function ********/
function main() {
    jQuery(document).ready(function($)
    {
        $.urlParam = function(name, url) {
            var results = new RegExp('[?|&|#]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url);
            return results[1] || 0;
        }

        $.loadCSS = function(href) {
            var cssLink = $("<link rel='stylesheet' type='text/css' href='"+href+"'>");
            $("head").append(cssLink);
        }

        $.findJS = function() {
            var elems = document.getElementsByTagName('script');
            var re = /.*slack-chat\.js/;

            for(var i = 0; i < elems.length; i++) {
                if(elems[i].src.match(re))
                    return elems[i];
            }

            throw "Could not find the script from Oghma.";
        }

        $.getSettings = function() {
            var el = $.findJS();
            var params = ['token'];
            var settings = new Object();

            for(var i = 0; i < params.length; i++) {
                var p = params[i];
                settings[p] = $.urlParam(p, el.src);
            }

            if (typeof window._LBSettings != 'undefined') {
                for (var attrname in window._LBSettings) {
                    settings[attrname] = window._LBSettings[attrname];
                }
            }

            return settings;
        }

        $.createButton = function() {
            var button = $('

');
            $("body").append(button);
        }

        $.loadJS = function(href) {
            var jsLink = $("");
            $("head").append(jsLink);
        }

        $.scrollChat = function(to) {
            $(to).animate({
                scrollTop: $(to).height()
            }, 'slow');
        }

        /**
        * Set cookie with name @var cookie with value @var value for @var days period.
        */
        $.setCookie = function(cookie, value, days) {
            days = days || 730; // 2 years

            var date = new Date();
            date.setTime(date.getTime() + (days*24*60*60*1000));

            // Set Cookie
            document.cookie = cookie + "=" + value + "; expires=" + date.toGMTString() + "; path=/";
        }

        /**
        * Get a cookie named @var cookie
        */
        $.getCookie = function(name) {
            var value = " " + document.cookie;
            var start = value.indexOf(" " + name + "=");
            if (start == -1) {
                value = null;
            }
            else {
                start = value.indexOf("=", start) + 1;
                var end = value.indexOf(";", start);
                if (end == -1) {
                    end = value.length;
                }
                value = unescape(value.substring(start,end));
            }
            return value;
        }

        /**
        * Gets the UUID for this user. If doesn't exist, creates a new UUID.
        */

        $.getUUID = function() {
            var cookieName = "oghma-uuid";
            if($.getCookie(cookieName) === null) {
                // Does not exists; Lets create a UUID for this user
                $.loadJS(baseURL + "/js/uuid.js");
                var cuuid = uuid.v4();
                $.setCookie(cookieName, cuuid);

                return cuuid;
            }
            return $.getCookie(cookieName);
        }

        $.getUserSystemInfo = function() {
            /**
            * JavaScript Client Detection
            * (C) viazenetti GmbH (Christian Ludwig)
            */

            var unknown = '-';

            // screen
            var screenSize = '';
            if (screen.width) {
                width = (screen.width) ? screen.width : '';
                height = (screen.height) ? screen.height : '';
                screenSize += '' + width + " x " + height;
            }

            //browser
            var nVer = navigator.appVersion;
            var nAgt = navigator.userAgent;
            var browser = navigator.appName;
            var version = '' + parseFloat(navigator.appVersion);
            var majorVersion = parseInt(navigator.appVersion, 10);
            var nameOffset, verOffset, ix;

            // Opera
            if ((verOffset = nAgt.indexOf('Opera')) != -1) {
                browser = 'Opera';
                version = nAgt.substring(verOffset + 6);
                if ((verOffset = nAgt.indexOf('Version')) != -1) {
                    version = nAgt.substring(verOffset + 8);
                }
            }
            // MSIE
            else if ((verOffset = nAgt.indexOf('MSIE')) != -1) {
                browser = 'Microsoft Internet Explorer';
                version = nAgt.substring(verOffset + 5);
            }
            // Chrome
            else if ((verOffset = nAgt.indexOf('Chrome')) != -1) {
                browser = 'Chrome';
                version = nAgt.substring(verOffset + 7);
            }
            // Safari
            else if ((verOffset = nAgt.indexOf('Safari')) != -1) {
                browser = 'Safari';
                version = nAgt.substring(verOffset + 7);
                if ((verOffset = nAgt.indexOf('Version')) != -1) {
                    version = nAgt.substring(verOffset + 8);
                }
            }
            // Firefox
            else if ((verOffset = nAgt.indexOf('Firefox')) != -1) {
                browser = 'Firefox';
                version = nAgt.substring(verOffset + 8);
            }
            // MSIE 11+
            else if (nAgt.indexOf('Trident/') != -1) {
                browser = 'Microsoft Internet Explorer';
                version = nAgt.substring(nAgt.indexOf('rv:') + 3);
            }
            // Other browsers
            else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
                browser = nAgt.substring(nameOffset, verOffset);
                version = nAgt.substring(verOffset + 1);
                if (browser.toLowerCase() == browser.toUpperCase()) {
                    browser = navigator.appName;
                }
            }
            // trim the version string
            if ((ix = version.indexOf(';')) != -1) version = version.substring(0, ix);
            if ((ix = version.indexOf(' ')) != -1) version = version.substring(0, ix);
            if ((ix = version.indexOf(')')) != -1) version = version.substring(0, ix);

            majorVersion = parseInt('' + version, 10);
            if (isNaN(majorVersion)) {
                version = '' + parseFloat(navigator.appVersion);
                majorVersion = parseInt(navigator.appVersion, 10);
            }

            // mobile version
            var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);

            // cookie
            var cookieEnabled = (navigator.cookieEnabled) ? true : false;

            if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) {
                document.cookie = 'testcookie';
                cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false;
            }

            // system
            var os = unknown;
            var clientStrings = [
            {s:'Windows 3.11', r:/Win16/},
            {s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
            {s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
            {s:'Windows 98', r:/(Windows 98|Win98)/},
            {s:'Windows CE', r:/Windows CE/},
            {s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
            {s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
            {s:'Windows Server 2003', r:/Windows NT 5.2/},
            {s:'Windows Vista', r:/Windows NT 6.0/},
            {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
            {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
            {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
            {s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
            {s:'Windows ME', r:/Windows ME/},
            {s:'Android', r:/Android/},
            {s:'Open BSD', r:/OpenBSD/},
            {s:'Sun OS', r:/SunOS/},
            {s:'Linux', r:/(Linux|X11)/},
            {s:'iOS', r:/(iPhone|iPad|iPod)/},
            {s:'Mac OS X', r:/Mac OS X/},
            {s:'Mac OS', r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
            {s:'QNX', r:/QNX/},
            {s:'UNIX', r:/UNIX/},
            {s:'BeOS', r:/BeOS/},
            {s:'OS/2', r:/OS\/2/},
            {s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
            ];

            for (var id in clientStrings) {
                var cs = clientStrings[id];
                if (cs.r.test(nAgt)) {
                    os = cs.s;
                    break;
                }
            }

            var osVersion = unknown;

            if (/Windows/.test(os)) {
                osVersion = /Windows (.*)/.exec(os)[1];
                os = 'Windows';
            }

            switch (os) {
            case 'Mac OS X':
                osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];
                break;

            case 'Android':
                osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1];
                break;

            case 'iOS':
                osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
                osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
                break;
            }

            var info = {
                screen: screenSize,
                browser: browser,
                browserVersion: version,
                mobile: mobile,
                os: os,
                osVersion: osVersion,
                cookies: cookieEnabled,
            };

            return info;
        }

        $.loadCSS(baseURL + "/slack-chat.css");
        $.loadJS( baseURL + "/socket.io/socket.io.js");

        $.createButton();

        $('#oghma-button').click(function() {

            var settings = $.getSettings();

            var messages = [];

            var messageField = document.getElementById('messageField');
            var content = document.getElementById('conversation');
            var ENTER_KEY_CODE = 13;

            // If they exist.
            var channel   = $.getCookie('oghma-channel');
            var signature = $.getCookie('oghma-signature');
            var userInfo  = $.getUserSystemInfo();

            $.ajax({
                url: baseURL + "/chat/create",
                method: 'POST',
                data: {
                    token:     settings.token,
                    channel:   channel,
                    signature: signature,
                    userInfo:  userInfo
                },
                success: function(data) {

                    console.log(data);

                    // Save connection to cookies
                    $.setCookie('oghma-channel',   data.channel);
                    $.setCookie('oghma-signature', data.signature);

                    var socket = io.connect(baseURL + '/chat');

                    var domContent = [
                        '',
                        '
Chat
',
                        '

',
                        '       <input type="text" name="message">',
                        '   ',
                        ].join('');

                    $('body').append(domContent);

                    $.scrollChat('#cbp-spmenu-s2 ul');

                    $('#cbp-spmenu-s2').toggleClass('cbp-spmenu-open' );

                    $('#cbp-spmenu-s2 input').bind('keypress', function(e){
                        // if enter key
                        if (e.keyCode == ENTER_KEY_CODE && $(this).val() != "") {
                            var message = $(this).val();

                            socket.emit('noncryptSend', {
                                message: message,
                            });

                            console.log("SEND: " + message);
                            $('#cbp-spmenu-s2 ul').append('
' + message + '
');

                            $.scrollChat('#cbp-spmenu-s2 ul');

                            $(this).val(''); // clear message field after sending
                        }
                    });

                    socket.on('connect', function(){
                        console.log("Connected to " + data.channel);
                        socket.emit('joinRoom', settings.token, data.channel, data.signature);
                    });

                    // On Slack message
                    socket.on('noncryptMessage', function (data) {
                        if(data.sender == "Other") {
                            $('#cbp-spmenu-s2 ul').append('
' + data.message + '
');
                            $.scrollChat('#cbp-spmenu-s2 ul');
                        }
                    });

                    socket.on('disconnect', function () {
                        $('#cbp-spmenu-s2 ul').append('
Server is now offline! :(
');
                        $.scrollChat('#cbp-spmenu-s2 ul');
                    });

                    socket.on('serverMessage', function (data) {
                        $('#cbp-spmenu-s2 ul').append('
Server: ' + data.message + '
');
                        $.scrollChat('#cbp-spmenu-s2 ul');
                    });
                }
            });

            // Get Channel from LB API with token
            // If no users online (not possible right now in Slack) show form for e-mail message.
            // Else create chat window and connect socket

        });

    });
}

})(); // We call our anonymous function immediately
<commit_msg>Flash page title<commit_after>(function() {

// Localize jQuery variable
var jQuery

var baseURL = "http://oghma.herokuapp.com";

if(window.location.hostname == "localhost")
    baseURL = "http://localhost:8888";

/******** Load jQuery if not present *********/
if (window.jQuery === undefined || window.jQuery.fn.jquery !== '2.1.1') {
    var script_tag = document.createElement('script');
    script_tag.setAttribute("type","text/javascript");
    script_tag.setAttribute("src",
        "//code.jquery.com/jquery-2.1.1.min.js");
    if (script_tag.readyState) {
      script_tag.onreadystatechange = function () { // For old versions of IE
          if (this.readyState == 'complete' || this.readyState == 'loaded') {
              scriptLoadHandler();
          }
      };
    } else { // Other browsers
      script_tag.onload = scriptLoadHandler;
    }
    // Try to find the head, otherwise default to the documentElement
    (document.getElementsByTagName("head")[0] || document.documentElement).appendChild(script_tag);
} else {
    // The jQuery version on the window is the one we want to use
    jQuery = window.jQuery;
    main();
}

/******** Called once jQuery has loaded ******/
function scriptLoadHandler() {
    // Restore $ and window.jQuery to their previous values and store the
    // new jQuery in our local jQuery variable
    jQuery = window.jQuery.noConflict(true);
    // Call our main function
    main();
}

/******** Our main function ********/
function main() {
    jQuery(document).ready(function($)
    {
        $.urlParam = function(name, url) {
            var results = new RegExp('[?|&|#]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url);
            return results[1] || 0;
        }

        $.loadCSS = function(href) {
            var cssLink = $("<link rel='stylesheet' type='text/css' href='"+href+"'>");
            $("head").append(cssLink);
        }

        $.findJS = function() {
            var elems = document.getElementsByTagName('script');
            var re = /.*slack-chat\.js/;

            for(var i = 0; i < elems.length; i++) {
                if(elems[i].src.match(re))
                    return elems[i];
            }

            throw "Could not find the script from Oghma.";
        }

        $.getSettings = function() {
            var el = $.findJS();
            var params = ['token'];
            var settings = new Object();

            for(var i = 0; i < params.length; i++) {
                var p = params[i];
                settings[p] = $.urlParam(p, el.src);
            }

            if (typeof window._LBSettings != 'undefined') {
                for (var attrname in window._LBSettings) {
                    settings[attrname] = window._LBSettings[attrname];
                }
            }

            return settings;
        }

        $.createButton = function() {
            var button = $('

');
            $("body").append(button);
        }

        $.loadJS = function(href) {
            var jsLink = $("");
            $("head").append(jsLink);
        }

        $.scrollChat = function(to) {
            $(to).animate({
                scrollTop: $(to).height()
            }, 'slow');
        }

        /**
        * Set cookie with name @var cookie with value @var value for @var days period.
        */
        $.setCookie = function(cookie, value, days) {
            days = days || 730; // 2 years

            var date = new Date();
            date.setTime(date.getTime() + (days*24*60*60*1000));

            // Set Cookie
            document.cookie = cookie + "=" + value + "; expires=" + date.toGMTString() + "; path=/";
        }

        /**
        * Get a cookie named @var cookie
        */
        $.getCookie = function(name) {
            var value = " " + document.cookie;
            var start = value.indexOf(" " + name + "=");
            if (start == -1) {
                value = null;
            }
            else {
                start = value.indexOf("=", start) + 1;
                var end = value.indexOf(";", start);
                if (end == -1) {
                    end = value.length;
                }
                value = unescape(value.substring(start,end));
            }
            return value;
        }

        /**
        * Gets the UUID for this user. If doesn't exist, creates a new UUID.
        */

        $.getUUID = function() {
            var cookieName = "oghma-uuid";
            if($.getCookie(cookieName) === null) {
                // Does not exists; Lets create a UUID for this user
                $.loadJS(baseURL + "/js/uuid.js");
                var cuuid = uuid.v4();
                $.setCookie(cookieName, cuuid);

                return cuuid;
            }
            return $.getCookie(cookieName);
        }

        /**
        * Get User Info
        */
        $.getUserSystemInfo = function() {
            /**
            * JavaScript Client Detection
            * (C) viazenetti GmbH (Christian Ludwig)
            */

            var unknown = '-';

            // screen
            var screenSize = '';
            if (screen.width) {
                width = (screen.width) ? screen.width : '';
                height = (screen.height) ? screen.height : '';
                screenSize += '' + width + " x " + height;
            }

            //browser
            var nVer = navigator.appVersion;
            var nAgt = navigator.userAgent;
            var browser = navigator.appName;
            var version = '' + parseFloat(navigator.appVersion);
            var majorVersion = parseInt(navigator.appVersion, 10);
            var nameOffset, verOffset, ix;

            // Opera
            if ((verOffset = nAgt.indexOf('Opera')) != -1) {
                browser = 'Opera';
                version = nAgt.substring(verOffset + 6);
                if ((verOffset = nAgt.indexOf('Version')) != -1) {
                    version = nAgt.substring(verOffset + 8);
                }
            }
            // MSIE
            else if ((verOffset = nAgt.indexOf('MSIE')) != -1) {
                browser = 'Microsoft Internet Explorer';
                version = nAgt.substring(verOffset + 5);
            }
            // Chrome
            else if ((verOffset = nAgt.indexOf('Chrome')) != -1) {
                browser = 'Chrome';
                version = nAgt.substring(verOffset + 7);
            }
            // Safari
            else if ((verOffset = nAgt.indexOf('Safari')) != -1) {
                browser = 'Safari';
                version = nAgt.substring(verOffset + 7);
                if ((verOffset = nAgt.indexOf('Version')) != -1) {
                    version = nAgt.substring(verOffset + 8);
                }
            }
            // Firefox
            else if ((verOffset = nAgt.indexOf('Firefox')) != -1) {
                browser = 'Firefox';
                version = nAgt.substring(verOffset + 8);
            }
            // MSIE 11+
            else if (nAgt.indexOf('Trident/') != -1) {
                browser = 'Microsoft Internet Explorer';
                version = nAgt.substring(nAgt.indexOf('rv:') + 3);
            }
            // Other browsers
            else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
                browser = nAgt.substring(nameOffset, verOffset);
                version = nAgt.substring(verOffset + 1);
                if (browser.toLowerCase() == browser.toUpperCase()) {
                    browser = navigator.appName;
                }
            }
            // trim the version string
            if ((ix = version.indexOf(';')) != -1) version = version.substring(0, ix);
            if ((ix = version.indexOf(' ')) != -1) version = version.substring(0, ix);
            if ((ix = version.indexOf(')')) != -1) version = version.substring(0, ix);

            majorVersion = parseInt('' + version, 10);
            if (isNaN(majorVersion)) {
                version = '' + parseFloat(navigator.appVersion);
                majorVersion = parseInt(navigator.appVersion, 10);
            }

            // mobile version
            var mobile = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(nVer);

            // cookie
            var cookieEnabled = (navigator.cookieEnabled) ? true : false;

            if (typeof navigator.cookieEnabled == 'undefined' && !cookieEnabled) {
                document.cookie = 'testcookie';
                cookieEnabled = (document.cookie.indexOf('testcookie') != -1) ? true : false;
            }

            // system
            var os = unknown;
            var clientStrings = [
            {s:'Windows 3.11', r:/Win16/},
            {s:'Windows 95', r:/(Windows 95|Win95|Windows_95)/},
            {s:'Windows ME', r:/(Win 9x 4.90|Windows ME)/},
            {s:'Windows 98', r:/(Windows 98|Win98)/},
            {s:'Windows CE', r:/Windows CE/},
            {s:'Windows 2000', r:/(Windows NT 5.0|Windows 2000)/},
            {s:'Windows XP', r:/(Windows NT 5.1|Windows XP)/},
            {s:'Windows Server 2003', r:/Windows NT 5.2/},
            {s:'Windows Vista', r:/Windows NT 6.0/},
            {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/},
            {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/},
            {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/},
            {s:'Windows NT 4.0', r:/(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/},
            {s:'Windows ME', r:/Windows ME/},
            {s:'Android', r:/Android/},
            {s:'Open BSD', r:/OpenBSD/},
            {s:'Sun OS', r:/SunOS/},
            {s:'Linux', r:/(Linux|X11)/},
            {s:'iOS', r:/(iPhone|iPad|iPod)/},
            {s:'Mac OS X', r:/Mac OS X/},
            {s:'Mac OS', r:/(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/},
            {s:'QNX', r:/QNX/},
            {s:'UNIX', r:/UNIX/},
            {s:'BeOS', r:/BeOS/},
            {s:'OS/2', r:/OS\/2/},
            {s:'Search Bot', r:/(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/}
            ];

            for (var id in clientStrings) {
                var cs = clientStrings[id];
                if (cs.r.test(nAgt)) {
                    os = cs.s;
                    break;
                }
            }

            var osVersion = unknown;

            if (/Windows/.test(os)) {
                osVersion = /Windows (.*)/.exec(os)[1];
                os = 'Windows';
            }

            switch (os) {
            case 'Mac OS X':
                osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];
                break;

            case 'Android':
                osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1];
                break;

            case 'iOS':
                osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
                osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
                break;
            }

            var info = {
                screen: screenSize,
                browser: browser,
                browserVersion: version,
                mobile: mobile,
                os: os,
                osVersion: osVersion,
                cookies: cookieEnabled,
            };

            return info;
        }

        /**
        * Flash page title
        */
        $.flashTitle = function () {
            var original = document.title;
            var timeout;

            window.flashTitle = function (newMsg, howManyTimes) {
                function step() {
                    document.title = (document.title == original) ? newMsg : original;

                    if (--howManyTimes > 0) {
                        timeout = setTimeout(step, 1000);
                    };
                };

                howManyTimes = parseInt(howManyTimes);

                if (isNaN(howManyTimes)) {
                    howManyTimes = 5;
                };

                cancelFlashTitle(timeout);
                step();
            };

            window.cancelFlashTitle = function () {
                clearTimeout(timeout);
                document.title = original;
            };
        };

        $.loadCSS(baseURL + "/slack-chat.css");
        $.loadJS( baseURL + "/socket.io/socket.io.js");

        $.createButton();

        $('#oghma-button').click(function() {

            var settings = $.getSettings();

            var messages = [];

            var messageField = document.getElementById('messageField');
            var content = document.getElementById('conversation');
            var ENTER_KEY_CODE = 13;

            // If they exist.
            var channel   = $.getCookie('oghma-channel');
            var signature = $.getCookie('oghma-signature');
            var userInfo  = $.getUserSystemInfo();

            $.ajax({
                url: baseURL + "/chat/create",
                method: 'POST',
                data: {
                    token:     settings.token,
                    channel:   channel,
                    signature: signature,
                    userInfo:  userInfo
                },
                success: function(data) {

                    console.log(data);

                    // Save connection to cookies
                    $.setCookie('oghma-channel',   data.channel);
                    $.setCookie('oghma-signature', data.signature);

                    var socket = io.connect(baseURL + '/chat');

                    var domContent = [
                        '',
                        '
Chat
',
                        '

',
                        '       <input type="text" name="message">',
                        '   ',
                        ].join('');

                    $('body').append(domContent);

                    $.scrollChat('#cbp-spmenu-s2 ul');

                    $('#cbp-spmenu-s2').toggleClass('cbp-spmenu-open' );

                    $('#cbp-spmenu-s2 input').bind('keypress', function(e){
                        // if enter key
                        if (e.keyCode == ENTER_KEY_CODE && $(this).val() != "") {
                            var message = $(this).val();

                            socket.emit('noncryptSend', {
                                message: message,
                            });

                            console.log("SEND: " + message);
                            $('#cbp-spmenu-s2 ul').append('
' + message + '
');

                            $.scrollChat('#cbp-spmenu-s2 ul');

                            $(this).val(''); // clear message field after sending
                        }
                    });

                    socket.on('connect', function(){
                        console.log("Connected to " + data.channel);
                        socket.emit('joinRoom', settings.token, data.channel, data.signature);
                    });

                    // On Slack message
                    socket.on('noncryptMessage', function (data) {
                        if(data.sender == "Other") {
                            $('#cbp-spmenu-s2 ul').append('
' + data.message + '
');
                            $.scrollChat('#cbp-spmenu-s2 ul');
                            $.flashTitle("New message", 3);
                        }
                    });

                    socket.on('disconnect', function () {
                        $('#cbp-spmenu-s2 ul').append('
Server is now offline! :(
');
                        $.scrollChat('#cbp-spmenu-s2 ul');
                    });

                    socket.on('serverMessage', function (data) {
                        $('#cbp-spmenu-s2 ul').append('
Server: ' + data.message + '
');
                        $.scrollChat('#cbp-spmenu-s2 ul');
                    });
                }
            });

            // Get Channel from LB API with token
            // If no users online (not possible right now in Slack) show form for e-mail message.
            // Else create chat window and connect socket

        });

    });
}

})(); // We call our anonymous function immediately
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports, module) {
    "use strict";

    var util = require("adapter/util"),
        descriptor = require("adapter/ps/descriptor"),
        toolLib = require("adapter/lib/tool");

    var Color = require("js/models/color"),
        Tool = require("js/models/tool"),
        shortcuts = require("js/util/shortcuts"),
        log = require("js/util/log");

    /**
     * Layers can be moved using type tool by holding down cmd
     * We need to reset the bounds correctly during this
     */
    var _moveHandler,
        _typeChangedHandler,
        _layerCreatedHandler,
        _layerDeletedHandler;

    /**
     * The createdTextLayer event results in an addLayers action which may or
     * may not replace an empty layer. Whether or not it does is recorded in
     * this variable. If we later receive a deletedTextLayer event, we will
     * only call removeLayer to undo the previous layer addition if there was
     * no replacement. Otherwise, if there was some replacement, we call
     * updateDocument instead becuase it's too hard to figure out what the new
     * correct layer state is.
     *
     * @private
     * @type {boolean}
     */
    var _layersReplaced = false;

    /**
     * @implements {Tool}
     * @constructor
     */
    var TypeTool = function () {
        var firstLaunch = true;

        var selectHandler = function () {
            // If this is set, means we didn't get to deselect the tool last time
            if (_moveHandler) {
                descriptor.removeListener("move", _moveHandler);
            }
            if (_typeChangedHandler) {
                descriptor.removeListener("updateTextProperties", _typeChangedHandler);
            }
            if (_layerCreatedHandler) {
                descriptor.removeListener("createTextLayer", _layerCreatedHandler);
            }

            _moveHandler = function () {
                var documentStore = this.flux.store("application"),
                    currentDocument = documentStore.getCurrentDocument();

                this.flux.actions.layers.resetBounds(currentDocument, currentDocument.layers.allSelected);
            }.bind(this);

            descriptor.addListener("move", _moveHandler);

            _typeChangedHandler = TypeTool.updateTextPropertiesHandler.bind(this);

            descriptor.addListener("updateTextProperties", _typeChangedHandler);

            _layerCreatedHandler = function (event) {
                var documentStore = this.flux.store("application"),
                    document = documentStore.getCurrentDocument();

                if (!document) {
                    log.error("Unexpected createTextLayer event: no active document");
                    return;
                }

                var layerID = event.layerID,
                    currentLayer = document.layers.byID(layerID);

                if (currentLayer) {
                    log.warn("Unexpected createTextLayer event for layer " + layerID);
                    this.flux.actions.layers.resetLayers(document, currentLayer);
                } else {
                    _layersReplaced = false;
                    this.flux.actions.layers.addLayers(document, layerID, true)
                        .then(function (layersReplaced) {
                            _layersReplaced = layersReplaced;
                        });
                }
            }.bind(this);

            descriptor.addListener("createTextLayer", _layerCreatedHandler);

            _layerDeletedHandler = function (event) {
                var documentStore = this.flux.store("application"),
                    document = documentStore.getCurrentDocument();

                if (!document) {
                    log.error("Unexpected deleteTextLayer event: no active document");
                    return;
                }

                var layerID = event.layerID,
                    layer = document.layers.byID(layerID);

                if (layer) {
                    if (_layersReplaced) {
                        // See comment above at the _layersReplaced declaration
                        this.flux.actions.documents.updateDocument();
                    } else {
                        this.flux.actions.layers.removeLayers(document, layer, true);
                    }
                } else {
                    log.warn("Unexpected deleteTextLayer event for layer " + layerID);
                }
            }.bind(this);

            descriptor.addListener("deleteTextLayer", _layerDeletedHandler);

            if (firstLaunch) {
                firstLaunch = false;

                return descriptor.playObject(toolLib.resetTypeTool("left", "Myriad Pro", 16, [0, 0, 0]));
            }
        };

        var deselectHandler = function () {
            descriptor.removeListener("move", _moveHandler);
            descriptor.removeListener("updateTextProperties", _typeChangedHandler);
            descriptor.removeListener("createTextLayer", _layerCreatedHandler);
            descriptor.removeListener("deleteTextLayer", _layerDeletedHandler);

            var documentStore = this.flux.store("application"),
                currentDocument = documentStore.getCurrentDocument();

            if (currentDocument) {
                var layers = currentDocument.layers.allSelected,
                    layersAllHaveType = layers.every(function (layer) {
                        return layer.text !== null;
                    });

                if (layersAllHaveType) {
                    this.flux.actions.layers.resetLayers(currentDocument, layers);
                }
            }

            _moveHandler = null;
            _typeChangedHandler = null;
        };

        Tool.call(this, "typeCreateOrEdit", "Type", "typeCreateOrEditTool", selectHandler, deselectHandler);

        this.activationKey = shortcuts.GLOBAL.TOOLS.TYPE;
    };
    util.inherits(TypeTool, Tool);

    /**
     * Handles updateTextProperties events, which are emitted during the type modal
     * tool state to indicate the text properties of the current text selection or
     * cursor position.
     *
     * @static
     * @param {object} event
     */
    TypeTool.updateTextPropertiesHandler = function (event) {
        var documentStore = this.flux.store("application"),
            currentDocument = documentStore.getCurrentDocument(),
            layers = currentDocument.layers.allSelected,
            typeLayers = layers.filter(function (layer) {
                return layer.kind === layer.layerKinds.TEXT;
            });

        if (typeLayers.isEmpty()) {
            return;
        }

        var properties = {
            textSize: event.hasOwnProperty("size") ? event.size : null,
            postScriptName: event.hasOwnProperty("fontName") ? event.fontName : null,
            color: event.hasOwnProperty("color") ? Color.fromPhotoshopColorObj(event.color, 100) : null,
            tracking: event.hasOwnProperty("tracking") ? event.tracking : null,
            alignment: event.hasOwnProperty("align") ? event.align : null,
            leading: event.autoLeading ? -1 : (event.hasOwnProperty("leading") ? event.leading : null)
        };

        this.flux.actions.type.updatePropertiesThrottled(currentDocument, layers, properties);
    };

    module.exports = TypeTool;
});
<commit_msg>Update updateTextProperties event for compatibility with a recent core API change.<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

define(function (require, exports, module) {
    "use strict";

    var util = require("adapter/util"),
        descriptor = require("adapter/ps/descriptor"),
        toolLib = require("adapter/lib/tool");

    var Color = require("js/models/color"),
        Tool = require("js/models/tool"),
        shortcuts = require("js/util/shortcuts"),
        log = require("js/util/log");

    /**
     * Layers can be moved using type tool by holding down cmd
     * We need to reset the bounds correctly during this
     */
    var _moveHandler,
        _typeChangedHandler,
        _layerCreatedHandler,
        _layerDeletedHandler;

    /**
     * The createdTextLayer event results in an addLayers action which may or
     * may not replace an empty layer. Whether or not it does is recorded in
     * this variable. If we later receive a deletedTextLayer event, we will
     * only call removeLayer to undo the previous layer addition if there was
     * no replacement. Otherwise, if there was some replacement, we call
     * updateDocument instead becuase it's too hard to figure out what the new
     * correct layer state is.
     *
     * @private
     * @type {boolean}
     */
    var _layersReplaced = false;

    /**
     * @implements {Tool}
     * @constructor
     */
    var TypeTool = function () {
        var firstLaunch = true;

        var selectHandler = function () {
            // If this is set, means we didn't get to deselect the tool last time
            if (_moveHandler) {
                descriptor.removeListener("move", _moveHandler);
            }
            if (_typeChangedHandler) {
                descriptor.removeListener("updateTextProperties", _typeChangedHandler);
            }
            if (_layerCreatedHandler) {
                descriptor.removeListener("createTextLayer", _layerCreatedHandler);
            }

            _moveHandler = function () {
                var documentStore = this.flux.store("application"),
                    currentDocument = documentStore.getCurrentDocument();

                this.flux.actions.layers.resetBounds(currentDocument, currentDocument.layers.allSelected);
            }.bind(this);

            descriptor.addListener("move", _moveHandler);

            _typeChangedHandler = TypeTool.updateTextPropertiesHandler.bind(this);

            descriptor.addListener("updateTextProperties", _typeChangedHandler);

            _layerCreatedHandler = function (event) {
                var documentStore = this.flux.store("application"),
                    document = documentStore.getCurrentDocument();

                if (!document) {
                    log.error("Unexpected createTextLayer event: no active document");
                    return;
                }

                var layerID = event.layerID,
                    currentLayer = document.layers.byID(layerID);

                if (currentLayer) {
                    log.warn("Unexpected createTextLayer event for layer " + layerID);
                    this.flux.actions.layers.resetLayers(document, currentLayer);
                } else {
                    _layersReplaced = false;
                    this.flux.actions.layers.addLayers(document, layerID, true)
                        .then(function (layersReplaced) {
                            _layersReplaced = layersReplaced;
                        });
                }
            }.bind(this);

            descriptor.addListener("createTextLayer", _layerCreatedHandler);

            _layerDeletedHandler = function (event) {
                var documentStore = this.flux.store("application"),
                    document = documentStore.getCurrentDocument();

                if (!document) {
                    log.error("Unexpected deleteTextLayer event: no active document");
                    return;
                }

                var layerID = event.layerID,
                    layer = document.layers.byID(layerID);

                if (layer) {
                    if (_layersReplaced) {
                        // See comment above at the _layersReplaced declaration
                        this.flux.actions.documents.updateDocument();
                    } else {
                        this.flux.actions.layers.removeLayers(document, layer, true);
                    }
                } else {
                    log.warn("Unexpected deleteTextLayer event for layer " + layerID);
                }
            }.bind(this);

            descriptor.addListener("deleteTextLayer", _layerDeletedHandler);

            if (firstLaunch) {
                firstLaunch = false;

                return descriptor.playObject(toolLib.resetTypeTool("left", "Myriad Pro", 16, [0, 0, 0]));
            }
        };

        var deselectHandler = function () {
            descriptor.removeListener("move", _moveHandler);
            descriptor.removeListener("updateTextProperties", _typeChangedHandler);
            descriptor.removeListener("createTextLayer", _layerCreatedHandler);
            descriptor.removeListener("deleteTextLayer", _layerDeletedHandler);

            var documentStore = this.flux.store("application"),
                currentDocument = documentStore.getCurrentDocument();

            if (currentDocument) {
                var layers = currentDocument.layers.allSelected,
                    layersAllHaveType = layers.every(function (layer) {
                        return layer.text !== null;
                    });

                if (layersAllHaveType) {
                    this.flux.actions.layers.resetLayers(currentDocument, layers);
                }
            }

            _moveHandler = null;
            _typeChangedHandler = null;
        };

        Tool.call(this, "typeCreateOrEdit", "Type", "typeCreateOrEditTool", selectHandler, deselectHandler);

        this.activationKey = shortcuts.GLOBAL.TOOLS.TYPE;
    };
    util.inherits(TypeTool, Tool);

    /**
     * Handles updateTextProperties events, which are emitted during the type modal
     * tool state to indicate the text properties of the current text selection or
     * cursor position.
     *
     * @static
     * @param {object} event
     */
    TypeTool.updateTextPropertiesHandler = function (event) {
        var documentStore = this.flux.store("application"),
            currentDocument = documentStore.getCurrentDocument(),
            layers = currentDocument.layers.allSelected,
            typeLayers = layers.filter(function (layer) {
                return layer.kind === layer.layerKinds.TEXT;
            });

        if (typeLayers.isEmpty()) {
            return;
        }

        var properties = {
            textSize: event.hasOwnProperty("size") ? event.size : null,
            postScriptName: event.hasOwnProperty("fontName") ? event.fontName : null,
            color: event.hasOwnProperty("color") ? Color.fromPhotoshopColorObj(event.color, 100) : null,
            tracking: event.hasOwnProperty("tracking") ? event.tracking : null,
            alignment: event.hasOwnProperty("align") ? event.align._value : null,
            leading: event.autoLeading ? -1 : (event.hasOwnProperty("leading") ? event.leading : null)
        };

        this.flux.actions.type.updatePropertiesThrottled(currentDocument, layers, properties);
    };

    module.exports = TypeTool;
});
<|endoftext|>


<commit_before>'use strict';

const Lf = require('lovefield');
require('sugar'); // monkeypatches Date

const Track = require('./track');
const Storage = require('./storage');

const Reporting = require('./reporting');

// {userId: <lovefield db>}
// lovefield docs say to avoid multiple calls to connect, even with a close in between.
// so, dbs are treated like singletons.
const _dbs = {};

// Return a lovefield db for this user.
// Will always return the same reference for a user on multiple calls.
exports.openDb = function openDb(userId, callback) {
  if (_dbs[userId]) {
    callback(_dbs[userId]);
    return;
  }

  console.log('opening db');
  const schemaBuilder = Lf.schema.create(`ltracks_${userId}`, 1);

  let table = schemaBuilder.createTable('Track');

  Track.fields.forEach(field => {
    table = table.addColumn(field.name, field.type);

    if (field.name === 'id') {
      table = table.addPrimaryKey(['id']);
    } else {
      table = table.addNullable([field.name]);
      if (!field.hidden) {
        table = table.addIndex(`idx_${field.name}`, [field.name], false, Lf.Order.DESC);
      }
    }
  });

  schemaBuilder.connect({storeType: Lf.schema.DataStoreType.MEMORY})
  .then(db => {
    _dbs[userId] = db;
    callback(db);
  })
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('schemaBuilder.connect', {
      extra: {err},
      stacktrace: true,
    });
  });
};

exports.upsertTracks = function upsertTracks(db, userId, tracks, callback) {
  console.log('cache: upserting', tracks.length, 'tracks');
  const track = db.getSchema().table('Track');

  const rows = [];
  for (let i = 0; i < tracks.length; i++) {
    rows.push(track.createRow(tracks[i]));
  }

  db.insertOrReplace().into(track).values(rows).exec()
  .then(callback)
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('db.insertOrReplace', {
      extra: {err},
      stacktrace: true,
    });
  });
};

exports.deleteTracks = function deleteTracks(db, userId, trackIds, callback) {
  console.log('cache: deleting', trackIds.length, 'tracks');
  const track = db.getSchema().table('Track');
  db.delete().from(track).where(track.id.in(trackIds)).exec()
  .then(callback)
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('db.delete', {
      extra: {err},
      stacktrace: true,
    });
  });
};

function getLinkedTracks(playlistId, splaylistcache, playlistsById, db, resultCache) {
  // Promise the tracks in a given playlist or splaylist.
  if (playlistId[0] === 'P') {
    // splaylist
    console.log('splaylist');
    let orderedEntries = [];
    try {
      orderedEntries = splaylistcache.splaylists[playlistId.substring(1)].orderedEntries;
    } catch (e) {
      // This is likely a desync between the rules and splaylist state.
      // It's often triggered on the first sync (since the cache is racing to sync first).
      console.error(e);
      Reporting.Raven.captureException(e, {
        level: 'warning',
        tags: {playlistId},
        extra: {splaylistcache},
      });
    }
    return Promise.resolve(orderedEntries.map(entry => entry.trackId));
  }
  // playlist
  console.log('playlist', playlistsById);
  const linkedPlaylist = playlistsById[playlistId];
  return new Promise(resolve => {
    exports.queryTracks(db, splaylistcache, linkedPlaylist, resultCache, resolve);
  }).then(tracks => tracks.map(t => t.id));
}

function titleMatches(operatorName, value, playlistTitle) {
  // Treating inclusive and exclusive operators the same way here is intentional.
  // Inverting matches here breaks exclusion, since it would:
  //   * find all playlists that _don't_ match the user's query
  //   * _exclude_ their tracks
  // when instead we want to _exclude_ the tracks in the playlists that _match_ their query.
  // See https://github.com/simon-weber/Autoplaylists-for-Google-Music/issues/163
  // for more details.
  const regexValue = Track.regexForOperator(operatorName, value);
  if (regexValue) {
    let res = playlistTitle.match(regexValue);
    if (Track.isExcludingOperator(operatorName)) {
      res = !res;
    }
    return res;
  }

  // Non regex string match.
  if (operatorName === 'eq' || operatorName === 'neq') {
    return playlistTitle === value;
  }

  console.error(`received unknown title operator ${operatorName}`);
  Reporting.Raven.captureMessage('received unknown title operator', {
    tags: {operatorName},
    extra: {value, playlistTitle},
  });

  return false;
}

function buildWhereClause(playlistId, track, playlistsById, splaylistcache, resultCache, db, rule) {
  // Promise a clause for use in a lovefield where() predicate, or null to select all tracks.
  let clause = null;
  let boolOp = null;

  if ('any' in rule) {
    boolOp = 'any';
  } else if ('all' in rule) {
    boolOp = 'all';
  }

  if (boolOp !== null) {
    // Handle boolean clauses.
    const subrules = rule[boolOp];
    const lfOp = boolOp === 'all' ? Lf.op.and : Lf.op.or;

    const subClausePs = subrules
    .map(buildWhereClause.bind(undefined, playlistId, track, playlistsById, splaylistcache, resultCache, db));

    return Promise.all(subClausePs)
    .then(subClauses => {
      // We need to handle nulls from recursive calls, as well as empty subrule arrays.
      const validClauses = subClauses.filter(c => c !== null);
      if (validClauses.length > 0) {
        clause = lfOp.apply(Lf.op, validClauses);
      }
      return clause;
    });
  }

  if ('value' in rule && 'operator' in rule) {
    // Handle leaf clauses.
    let value = rule.value;
    let operator = rule.operator;

    if (rule.name === 'playlist' || rule.name === 'playlistTitle') {
      const linkedPlaylistIds = [];
      if (rule.name === 'playlistTitle') {
        for (const id in playlistsById) {
          if (titleMatches(rule.operator, rule.value, playlistsById[id].title)) {
            if (id !== playlistId) {
              console.log('matched playlist', playlistsById[id].title);
              linkedPlaylistIds.push(id);
            }
          }
        }
        for (const id in splaylistcache.splaylists) {
          const splaylist = splaylistcache.splaylists[id];
          if (!splaylist.isAutoplaylist && titleMatches(rule.operator, rule.value, splaylist.title)) {
            console.log('matched splaylist', splaylist.title);
            linkedPlaylistIds.push('P' + id);
          }
        }
      } else {
        linkedPlaylistIds.push(rule.value);
      }

      return Promise.all(linkedPlaylistIds.map(id => getLinkedTracks(id, splaylistcache, playlistsById, db, resultCache)))
      .then(linkedTrackIdsBatch => {
        const allLinkedTrackIds = new Set();
        for (let i = 0; i < linkedTrackIdsBatch.length; i++) {
          const trackIds = linkedTrackIdsBatch[i];
          for (let j = 0; j < trackIds.length; j++) {
            allLinkedTrackIds.add(trackIds[j]);
          }
        }
        return Array.from(allLinkedTrackIds);
      }).then(trackIdList => {
        console.debug(`found ${trackIdList.length} linked tracks`);
        clause = Lf.op.or(
          track.id.in(trackIdList),
          track.storeId.in(trackIdList)
        );
        if (Track.isExcludingOperator(rule.operator)) {
          clause = Lf.op.not(clause);
        }

        return clause;
      });
    }
    // non-playlist leaf
    if (Track.fieldsByName[rule.name].is_datetime) {
      value = Date.create(rule.value).getTime() * 1000;
    }

    const regexValue = Track.regexForOperator(rule.operator, value);
    if (regexValue) {
      operator = 'match';
      value = regexValue;
    }

    clause = track[rule.name][operator](value);
    return Promise.resolve(clause);
  }
  console.error('should have returned already');
}

function execQuery(db, track, whereClause, playlist, callback, onError) {
  let query = db.select().from(track);

  if (whereClause !== null) {
    query = query.where(whereClause);
  }

  for (let i = 0; i < playlist.sorts.length; i++) {
    const sort = playlist.sorts[i];
    const orderBy = track[sort.sortBy];
    const order = Lf.Order[sort.sortByOrder];
    query = query.orderBy(orderBy, order);
  }

  if (playlist.limit) {
    query = query.limit(playlist.limit);
  }

  query.exec().then(callback).catch(onError);
}

exports.queryTracks = function queryTracks(db, splaylistcache, playlist, resultCache, callback) {
  // Callback a list of tracks that should be in the playlist, or null on problems.

  if (playlist.localId in resultCache) {
    console.info('using cached results for', playlist.localId, resultCache);
    return callback(resultCache[playlist.localId]);
  }

  Storage.getPlaylistsForUser(playlist.userId, playlists => {
    const playlistsById = {};
    for (let i = 0; i < playlists.length; i++) {
      const p = playlists[i];
      playlistsById[p.localId] = p;
    }

    const track = db.getSchema().table('Track');
    buildWhereClause(playlist.localId, track, playlistsById, splaylistcache, resultCache, db, playlist.rules)
    .then(whereClause => {
      execQuery(db, track, whereClause, playlist, results => {
        resultCache[playlist.localId] = results;  // eslint-disable-line no-param-reassign
        callback(results);
      }, err => {
        console.error('execQuery', err);
        Reporting.Raven.captureMessage('execQuery', {
          tags: {playlistId: playlist.remoteId},
          extra: {playlist, err},
          stacktrace: true,
        });
        return callback(null);
      });
    })
    .catch(e => {
      console.error(e);
      Reporting.Raven.captureException(e, {
        tags: {playlistId: playlist.remoteId},
        extra: {playlist},
      });
      return callback(null);
    });
  });
};

exports.orderTracks = function orderTracks(db, playlist, trackIds, callback, onError) {
  // Return a copy of tracks, ordered by playlist's sort rules.
  // Tracks that wouldn't normally be in playlist are allowed.

  const track = db.getSchema().table('Track');
  const whereClause = track.id.in(trackIds);

  execQuery(db, track, whereClause, playlist, callback, onError);
};
<commit_msg>fix cycle detection [closes #164]<commit_after>'use strict';

const Lf = require('lovefield');
require('sugar'); // monkeypatches Date

const Track = require('./track');
const Storage = require('./storage');

const Reporting = require('./reporting');

// {userId: <lovefield db>}
// lovefield docs say to avoid multiple calls to connect, even with a close in between.
// so, dbs are treated like singletons.
const _dbs = {};

// Return a lovefield db for this user.
// Will always return the same reference for a user on multiple calls.
exports.openDb = function openDb(userId, callback) {
  if (_dbs[userId]) {
    callback(_dbs[userId]);
    return;
  }

  console.log('opening db');
  const schemaBuilder = Lf.schema.create(`ltracks_${userId}`, 1);

  let table = schemaBuilder.createTable('Track');

  Track.fields.forEach(field => {
    table = table.addColumn(field.name, field.type);

    if (field.name === 'id') {
      table = table.addPrimaryKey(['id']);
    } else {
      table = table.addNullable([field.name]);
      if (!field.hidden) {
        table = table.addIndex(`idx_${field.name}`, [field.name], false, Lf.Order.DESC);
      }
    }
  });

  schemaBuilder.connect({storeType: Lf.schema.DataStoreType.MEMORY})
  .then(db => {
    _dbs[userId] = db;
    callback(db);
  })
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('schemaBuilder.connect', {
      extra: {err},
      stacktrace: true,
    });
  });
};

exports.upsertTracks = function upsertTracks(db, userId, tracks, callback) {
  console.log('cache: upserting', tracks.length, 'tracks');
  const track = db.getSchema().table('Track');

  const rows = [];
  for (let i = 0; i < tracks.length; i++) {
    rows.push(track.createRow(tracks[i]));
  }

  db.insertOrReplace().into(track).values(rows).exec()
  .then(callback)
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('db.insertOrReplace', {
      extra: {err},
      stacktrace: true,
    });
  });
};

exports.deleteTracks = function deleteTracks(db, userId, trackIds, callback) {
  console.log('cache: deleting', trackIds.length, 'tracks');
  const track = db.getSchema().table('Track');
  db.delete().from(track).where(track.id.in(trackIds)).exec()
  .then(callback)
  .catch(err => {
    console.error(err);
    Reporting.Raven.captureMessage('db.delete', {
      extra: {err},
      stacktrace: true,
    });
  });
};

function getLinkedTracks(playlistId, splaylistcache, playlistsById, db, resultCache) {
  // Promise the tracks in a given playlist or splaylist.
  // Cycle detection happens here so it applies to both static and auto playlists.

  if (playlistId in resultCache) {
    console.info('using cached results for', playlistId, resultCache);
    return Promise.resolve(resultCache[playlistId]);
  }

  // This terminates cycles with the empty set.
  // If we're able to get a result, the cache gets overwritten below.
  resultCache[playlistId] = []; // eslint-disable-line no-param-reassign

  if (playlistId[0] === 'P') {
    // splaylist
    console.debug('getLinked splaylist', playlistsById);
    let orderedEntries = [];
    try {
      orderedEntries = splaylistcache.splaylists[playlistId.substring(1)].orderedEntries;
    } catch (e) {
      // This is likely a desync between the rules and splaylist state.
      // It's often triggered on the first sync (since the cache is racing to sync first).
      console.error(e);
      Reporting.Raven.captureException(e, {
        level: 'warning',
        tags: {playlistId},
        extra: {splaylistcache},
      });
    }
    const result = orderedEntries.map(entry => entry.trackId);
    resultCache[playlistId] = result; // eslint-disable-line no-param-reassign
    return Promise.resolve(result);
  }
  // playlist
  console.debug('getLinked playlist', playlistsById);
  const linkedPlaylist = playlistsById[playlistId];
  return new Promise(resolve => {
    exports.queryTracks(db, splaylistcache, linkedPlaylist, resultCache, resolve);
  }).then(tracks => {
    const result = tracks.map(t => t.id);
    resultCache[playlistId] = result; // eslint-disable-line no-param-reassign
    return result;
  });
}

function titleMatches(operatorName, value, playlistTitle) {
  // Treating inclusive and exclusive operators the same way here is intentional.
  // Inverting matches here breaks exclusion, since it would:
  //   * find all playlists that _don't_ match the user's query
  //   * _exclude_ their tracks
  // when instead we want to _exclude_ the tracks in the playlists that _match_ their query.
  // See https://github.com/simon-weber/Autoplaylists-for-Google-Music/issues/163
  // for more details.
  const regexValue = Track.regexForOperator(operatorName, value);
  if (regexValue) {
    let res = playlistTitle.match(regexValue);
    if (Track.isExcludingOperator(operatorName)) {
      res = !res;
    }
    return res;
  }

  // Non regex string match.
  if (operatorName === 'eq' || operatorName === 'neq') {
    return playlistTitle === value;
  }

  console.error(`received unknown title operator ${operatorName}`);
  Reporting.Raven.captureMessage('received unknown title operator', {
    tags: {operatorName},
    extra: {value, playlistTitle},
  });

  return false;
}

function buildWhereClause(playlistId, track, playlistsById, splaylistcache, resultCache, db, rule) {
  // Promise a clause for use in a lovefield where() predicate, or null to select all tracks.
  let clause = null;
  let boolOp = null;

  if ('any' in rule) {
    boolOp = 'any';
  } else if ('all' in rule) {
    boolOp = 'all';
  }

  if (boolOp !== null) {
    // Handle boolean clauses.
    const subrules = rule[boolOp];
    const lfOp = boolOp === 'all' ? Lf.op.and : Lf.op.or;

    const subClausePs = subrules
    .map(buildWhereClause.bind(undefined, playlistId, track, playlistsById, splaylistcache, resultCache, db));

    return Promise.all(subClausePs)
    .then(subClauses => {
      // We need to handle nulls from recursive calls, as well as empty subrule arrays.
      const validClauses = subClauses.filter(c => c !== null);
      if (validClauses.length > 0) {
        clause = lfOp.apply(Lf.op, validClauses);
      }
      return clause;
    });
  }

  if ('value' in rule && 'operator' in rule) {
    // Handle leaf clauses.
    let value = rule.value;
    let operator = rule.operator;

    if (rule.name === 'playlist' || rule.name === 'playlistTitle') {
      const linkedPlaylistIds = [];
      if (rule.name === 'playlistTitle') {
        for (const id in playlistsById) {
          if (titleMatches(rule.operator, rule.value, playlistsById[id].title)) {
            if (id !== playlistId) {
              console.log('matched playlist', playlistsById[id].title);
              linkedPlaylistIds.push(id);
            }
          }
        }
        for (const id in splaylistcache.splaylists) {
          const splaylist = splaylistcache.splaylists[id];
          if (!splaylist.isAutoplaylist && titleMatches(rule.operator, rule.value, splaylist.title)) {
            console.log('matched splaylist', splaylist.title);
            linkedPlaylistIds.push('P' + id);
          }
        }
      } else {
        linkedPlaylistIds.push(rule.value);
      }

      return Promise.all(linkedPlaylistIds.map(id => getLinkedTracks(id, splaylistcache, playlistsById, db, resultCache)))
      .then(linkedTrackIdsBatch => {
        const allLinkedTrackIds = new Set();
        for (let i = 0; i < linkedTrackIdsBatch.length; i++) {
          const trackIds = linkedTrackIdsBatch[i];
          for (let j = 0; j < trackIds.length; j++) {
            allLinkedTrackIds.add(trackIds[j]);
          }
        }
        return Array.from(allLinkedTrackIds);
      }).then(trackIdList => {
        console.debug(`found ${trackIdList.length} linked tracks`);
        clause = Lf.op.or(
          track.id.in(trackIdList),
          track.storeId.in(trackIdList)
        );
        if (Track.isExcludingOperator(rule.operator)) {
          clause = Lf.op.not(clause);
        }

        return clause;
      });
    }
    // non-playlist leaf
    if (Track.fieldsByName[rule.name].is_datetime) {
      value = Date.create(rule.value).getTime() * 1000;
    }

    const regexValue = Track.regexForOperator(rule.operator, value);
    if (regexValue) {
      operator = 'match';
      value = regexValue;
    }

    clause = track[rule.name][operator](value);
    return Promise.resolve(clause);
  }
  console.error('should have returned already');
}

function execQuery(db, track, whereClause, playlist, callback, onError) {
  let query = db.select().from(track);

  if (whereClause !== null) {
    query = query.where(whereClause);
  }

  for (let i = 0; i < playlist.sorts.length; i++) {
    const sort = playlist.sorts[i];
    const orderBy = track[sort.sortBy];
    const order = Lf.Order[sort.sortByOrder];
    query = query.orderBy(orderBy, order);
  }

  if (playlist.limit) {
    query = query.limit(playlist.limit);
  }

  query.exec().then(callback).catch(onError);
}

exports.queryTracks = function queryTracks(db, splaylistcache, playlist, resultCache, callback) {
  // Callback a list of tracks that should be in the playlist, or null on problems.
  console.debug('queryTracks', playlist.localId, playlist.remoteId, playlist);

  Storage.getPlaylistsForUser(playlist.userId, playlists => {
    const playlistsById = {};
    for (let i = 0; i < playlists.length; i++) {
      const p = playlists[i];
      playlistsById[p.localId] = p;
    }

    const track = db.getSchema().table('Track');
    buildWhereClause(playlist.localId, track, playlistsById, splaylistcache, resultCache, db, playlist.rules)
    .then(whereClause => {
      execQuery(db, track, whereClause, playlist, results => {
        callback(results);
      }, err => {
        console.error('execQuery', err);
        Reporting.Raven.captureMessage('execQuery', {
          tags: {playlistId: playlist.remoteId},
          extra: {playlist, err},
          stacktrace: true,
        });
        return callback(null);
      });
    })
    .catch(e => {
      console.error(e);
      Reporting.Raven.captureException(e, {
        tags: {playlistId: playlist.remoteId},
        extra: {playlist},
      });
      return callback(null);
    });
  });
};

exports.orderTracks = function orderTracks(db, playlist, trackIds, callback, onError) {
  // Return a copy of tracks, ordered by playlist's sort rules.
  // Tracks that wouldn't normally be in playlist are allowed.

  const track = db.getSchema().table('Track');
  const whereClause = track.id.in(trackIds);

  execQuery(db, track, whereClause, playlist, callback, onError);
};
<|endoftext|>


<commit_before>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('gb.codeView', {

        options: {
            model: null,
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            $('<div/>').addClass(this.widgetName)
                .text(this.widgetName)
                .appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.tools.remove();
        },

        resize: function(event, widget) {
            widget = widget || this;
            $(widget._editor.getScrollerElement())
                .css('height', widget.element.height());
            widget._editor.refresh();
            widget._selectCode(widget._htmlDoc.doc,
                    widget.options.model.getSelected()?
                    widget.options.model.getSelected():
                    widget.options.model.getActivePage().getUid());
        },

        refresh: function(event, widget) {
            var self = this, textCode;

            widget = widget || this;
            self._htmlDoc = generateHTML();
            textCode = $(self.element).find('#text-code');

            if (textCode.length === 0) {
                self.element.find('*').remove();
                textCode = $('<textarea></textarea>')
                    .attr({'id': 'text-code',
                           'readonly': 'readonly'})
                    .css({ 'overflow': 'auto',
                           'resize':  'none',
                           'min-height': '100%',
                           'width': '100%',
                           'border': 0,
                           'margin': 0,
                           'padding': 0 });
                self.element.append(textCode);
                self.element.css('overflow','visible');
                widget._editor = CodeMirror.fromTextArea(textCode[0],
                        {mode: "text/gbsrc", tabMode: "indent", readOnly: true});
                $(window).resize(function() {
                    widget.resize();
                });
            }

            widget._editor.setValue(self._htmlDoc.html);
        },

        // Private functions
        _createPrimaryTools: function() {
            var tools =$('<div/>'), button,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            tools.addClass('hbox').hide();
            $.each(commands, function(i, t) {
                button =$('<button/>').addClass("buttonStyle ui-state-default")
                .attr('id', 'btn' + t)
                .appendTo(tools);
            });
            return tools;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            widget = widget || this;
            widget._selectCode(widget._htmlDoc.doc, event.uid);
        },

        _activePageChangedHandler: function(event, widget) {
            widget = widget || this;
            if (!event.page || event.page === undefined ||
                !widget.options.model) {
                return;
            }
            widget.refresh();
            if (widget._editor)
                widget._editor.setValue(widget._htmlDoc.html);
            widget._selectCode(widget._htmlDoc.doc, event.page.getUid());
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh();
        },

        _selectCode: function (resultDoc, selectedUid) {
            var widget = this;
            $(resultDoc).find(':data(uid)').each(function () {
                if ($(this).data('uid') === selectedUid) {
                    var findNodesByHTML = function (node, html) {
                        var nodes = [];
                        if (node.outerHTML === html) {
                            nodes.push(node);
                        }
                        else $.each($(node).children(), function () {
                            $.merge(nodes, findNodesByHTML(this, html));
                        });
                        return nodes;
                     },
                        selectedHTML =
                            formatHTML(xmlserializer.serializeToString(this)),
                        similarNodes =
                            findNodesByHTML(this.ownerDocument.documentElement,
                                    this.outerHTML),
                        index = $.inArray(this, similarNodes),
                        //Sometimes, jQM use multiple nodes to define a widget,
                        //so we have to search for the combined html
                        nextNode = $(this),
                        regNode, matched, matchedIndex = 0;
                     while ((nextNode = nextNode.next())[0]){
                        if (nextNode.data('uid') === selectedUid)
                            selectedHTML += ("\n" +
                                    formatHTML(xmlserializer
                                        .serializeToString(nextNode[0])));
                     }
                     regNode =
                         new RegExp(selectedHTML.split(/ *\n */).join("\\s*"),'mg');

                     while (matched = regNode.exec(widget._htmlDoc.html)){
                         if (matchedIndex ++ === index ) {
                             var textCode = widget._editor.getScrollerElement();
                             widget._editor.setSelection
                                 (widget._editor.posFromIndex(matched.index),
                                  widget._editor.posFromIndex(regNode.lastIndex));
                             textCode.scrollTop =
                                 textCode.scrollHeight/widget._htmlDoc.html.length
                                 * matched.index - textCode.clientHeight/2;
                             break;
                         }
                     }
                     return false;
               }
            });
        },
    });
})(jQuery);
<commit_msg>[Code View] Fix bug where getUid() can be called on a null value<commit_after>/*
 * gui-builder - A simple WYSIWYG HTML5 app creator
 * Copyright (c) 2011, Intel Corporation.
 *
 * This program is licensed under the terms and conditions of the
 * Apache License, version 2.0.  The full text of the Apache License is at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 */
"use strict";

// Layout view widget

(function($, undefined) {

    $.widget('gb.codeView', {

        options: {
            model: null,
        },

        _create: function() {
            var o = this.options,
                e = this.element;

            o.designReset = this._designResetHandler;
            o.selectionChanged = this._selectionChangedHandler;
            o.activePageChanged = this._activePageChangedHandler;
            o.modelUpdated = this._modelUpdatedHandler;

            // FIXME: This should work, but $.extend of options seems to be
            //        creating a copy of the ADM, which will not containt the
            //        same nodes and events as the master
            //o.model = o.model || ADM || undefined;
            if (o.model) {
                this._bindADMEvents(o.model);
            }

            $('<div/>').addClass(this.widgetName)
                .text(this.widgetName)
                .appendTo(this.element);

            this.options.primaryTools = this._createPrimaryTools();
            this.options.secondaryTools = this._createSecondaryTools();

            this.refresh(null, this);

            return this;
        },

        _setOption: function(key, value) {
            switch (key) {
                // Should this REALLY be done here, or plugin registration in
                // the "host"... using the functions mapped in widget options?
                case 'model':
                    this._unbindADMEvents();
                    this._bindADMEvents(value);
                    this.refresh(null, this);
                    break;
                default:
                    break;
            }
        },

        destroy: function() {
            // TODO: unbind any ADM event handlers
            $(this.element).find('.'+this.widgetName).remove();
            this.options.tools.remove();
        },

        resize: function(event, widget) {
            var selected, activePage;
            widget = widget || this;
            $(widget._editor.getScrollerElement())
                .css('height', widget.element.height());
            widget._editor.refresh();

            selected = widget.options.model.getSelected();
            if (!selected) {
                activePage = widget.options.model.getActivePage();
                if (activePage) {
                    selected = activePage.getUid();
                }
            }
        },

        refresh: function(event, widget) {
            var self = this, textCode;

            widget = widget || this;
            self._htmlDoc = generateHTML();
            textCode = $(self.element).find('#text-code');

            if (textCode.length === 0) {
                self.element.find('*').remove();
                textCode = $('<textarea></textarea>')
                    .attr({'id': 'text-code',
                           'readonly': 'readonly'})
                    .css({ 'overflow': 'auto',
                           'resize':  'none',
                           'min-height': '100%',
                           'width': '100%',
                           'border': 0,
                           'margin': 0,
                           'padding': 0 });
                self.element.append(textCode);
                self.element.css('overflow','visible');
                widget._editor = CodeMirror.fromTextArea(textCode[0],
                        {mode: "text/gbsrc", tabMode: "indent", readOnly: true});
                $(window).resize(function() {
                    widget.resize();
                });
            }

            widget._editor.setValue(self._htmlDoc.html);
        },

        // Private functions
        _createPrimaryTools: function() {
            var tools =$('<div/>'), button,
                commands = ['undo', 'redo', 'cut', 'copy', 'paste'];

            tools.addClass('hbox').hide();
            $.each(commands, function(i, t) {
                button =$('<button/>').addClass("buttonStyle ui-state-default")
                .attr('id', 'btn' + t)
                .appendTo(tools);
            });
            return tools;
        },

        _createSecondaryTools: function() {
            return $(null);
        },

        _bindADMEvents: function(a) {
            var o = this.options,
                d = this.designRoot;

            if (a) {
                o.model = a;

                if (o.designReset) {
                    a.bind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.bind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.bind("activePageChanged", o.activePageChanged, this);
                }

                // Since model changed, need to call our designReset hander
                // to sync up the ADMDesign modelUpdated event handler
                if (o.designReset) {
                    o.designReset({design: a.getDesignRoot()}, this);
                }
            }
        },

        _unbindADMEvents: function() {
            var o = this.options,
                a = this.options.model,
                d = this.designRoot;

            // First unbind our ADMDesign modelUpdated handler, if any...
            if (d && o.modelUpdated) {
                d.designRoot.unbind("modelUpdated", o.modelUpdated, this);
            }

            // Now unbind all ADM model event handlers, if any...
            if (a) {
                if (o.designReset) {
                    a.unbind("designReset", o.designReset, this);
                }
                if (o.selectionChanged) {
                    a.unbind("selectionChanged", o.selectionChanged, this);
                }
                if (o.activePageChanged) {
                    a.unbind("activePageChanged", o.activePageChanged, this);
                }
            }
        },

        _designResetHandler: function(event, widget) {
            var d = event && event.design, o;

            widget = widget || this;
            o = widget.options;
            d = d || o.model.getDesignRoot();

            // Do nothing if the new ADMDesign equals our currently cached one
            if (d === widget.designRoot) {
                return;
            }

            // First, unbind existing modelUpdated hander, if any...
            if (widget.designRoot && o.modelUpdated) {
                widget.designRoot.unbind("modelUpdated", o.modelUpdated,widget);
            }

            // Next, bind to modelUpdated events from new ADMDesign, if any...
            if (d && o.modelUpdated) {
                d.bind("modelUpdated", o.modelUpdated, widget);
            }

            // Then, cache the new ADMDesign reference with this instance
            widget.designRoot = d;

            // Finally, redraw our view since the ADMDesign root has changed
            widget.refresh(event, widget);
        },

        _selectionChangedHandler: function(event, widget) {
            widget = widget || this;
            widget._selectCode(widget._htmlDoc.doc, event.uid);
        },

        _activePageChangedHandler: function(event, widget) {
            widget = widget || this;
            if (!event.page || event.page === undefined ||
                !widget.options.model) {
                return;
            }
            widget.refresh();
            if (widget._editor)
                widget._editor.setValue(widget._htmlDoc.html);
            widget._selectCode(widget._htmlDoc.doc, event.page.getUid());
        },

        _modelUpdatedHandler: function(event, widget) {
            widget = widget || this;
            widget.refresh();
        },

        _selectCode: function (resultDoc, selectedUid) {
            var widget = this;
            $(resultDoc).find(':data(uid)').each(function () {
                if ($(this).data('uid') === selectedUid) {
                    var findNodesByHTML = function (node, html) {
                        var nodes = [];
                        if (node.outerHTML === html) {
                            nodes.push(node);
                        }
                        else $.each($(node).children(), function () {
                            $.merge(nodes, findNodesByHTML(this, html));
                        });
                        return nodes;
                     },
                        selectedHTML =
                            formatHTML(xmlserializer.serializeToString(this)),
                        similarNodes =
                            findNodesByHTML(this.ownerDocument.documentElement,
                                    this.outerHTML),
                        index = $.inArray(this, similarNodes),
                        //Sometimes, jQM use multiple nodes to define a widget,
                        //so we have to search for the combined html
                        nextNode = $(this),
                        regNode, matched, matchedIndex = 0;
                     while ((nextNode = nextNode.next())[0]){
                        if (nextNode.data('uid') === selectedUid)
                            selectedHTML += ("\n" +
                                    formatHTML(xmlserializer
                                        .serializeToString(nextNode[0])));
                     }
                     regNode =
                         new RegExp(selectedHTML.split(/ *\n */).join("\\s*"),'mg');

                     while (matched = regNode.exec(widget._htmlDoc.html)){
                         if (matchedIndex ++ === index ) {
                             var textCode = widget._editor.getScrollerElement();
                             widget._editor.setSelection
                                 (widget._editor.posFromIndex(matched.index),
                                  widget._editor.posFromIndex(regNode.lastIndex));
                             textCode.scrollTop =
                                 textCode.scrollHeight/widget._htmlDoc.html.length
                                 * matched.index - textCode.clientHeight/2;
                             break;
                         }
                     }
                     return false;
               }
            });
        },
    });
})(jQuery);
<|endoftext|>


<commit_before>import {resolve} from 'path';
import {
  DefinePlugin,
  HotModuleReplacementPlugin,
  NamedModulesPlugin,
  NoEmitOnErrorsPlugin,
} from 'webpack';
import CleanPlugin from 'clean-webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import HTMLPlugin from 'html-webpack-plugin';
import PrepackPlugin from 'prepack-webpack-plugin';
import OptimizeJSPlugin from 'optimize-js-plugin';
import BabiliPlugin from 'babili-webpack-plugin';
import ExtractTextPlugin, {extract} from 'extract-text-webpack-plugin';
import OfflinePlugin from 'offline-plugin';

const host = 'localhost';
const port = process.env.PORT || 3000;
const url = `http://${host}:${port}/`;

const src = 'src';
const dest = 'app';
const clean = [dest];
const copy = [
  {
    from: 'content',
  },
];

function config({dev} = {}) {
  const env = dev ? 'development' : 'production';

  return {
    devtool: dev ? 'eval-source-map' : 'hidden-source-map',
    entry: [
      ...dev ? [
        `webpack-dev-server/client?${url}`,
        'webpack/hot/only-dev-server',
        'react-hot-loader/patch',
      ] : [],
      'babel-polyfill',
      `./${src}/index.jsx`,
    ],
    output: {
      path: resolve(__dirname, dest),
      filename: `bundle${dev ? '' : '.[chunkhash]'}.js`,
    },
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          include: resolve(__dirname, src),
          loader: 'babel-loader',
          options: {
            cacheDirectory: dev,
            forceEnv: env,
          },
        },
        {
          test: /\.css$/,
          include: resolve(__dirname, src),
          use: dev ? [
            'style-loader',
            {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                modules: true,
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
            },
          ] : extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                modules: true,
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
            },
          }),
        },
      ],
    },
    plugins: [
      new CleanPlugin(clean),
      new CopyPlugin(copy),
      new HTMLPlugin({
        template: `${src}/template.ejs`,
        inject: false,
      }),
      new DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(env),
      }),
      ...dev ? [
        new HotModuleReplacementPlugin(),
        new NamedModulesPlugin(),
        new NoEmitOnErrorsPlugin(),
      ] : [
        new PrepackPlugin(),
        new OptimizeJSPlugin(),
        new BabiliPlugin(),
        new ExtractTextPlugin({
          filename: `style${dev ? '' : '.[contenthash]'}.css`,
          allChunks: true,
        }),
        new OfflinePlugin(),
      ],
    ],
    resolve: {
      extensions: ['.js', '.jsx', '.json'],
    },
  };
}

export {
  config as default,
};
<commit_msg>Temporary disable prepack plugin<commit_after>import {resolve} from 'path';
import {
  DefinePlugin,
  HotModuleReplacementPlugin,
  NamedModulesPlugin,
  NoEmitOnErrorsPlugin,
} from 'webpack';
import CleanPlugin from 'clean-webpack-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import HTMLPlugin from 'html-webpack-plugin';
// import PrepackPlugin from 'prepack-webpack-plugin';
import OptimizeJSPlugin from 'optimize-js-plugin';
import BabiliPlugin from 'babili-webpack-plugin';
import ExtractTextPlugin, {extract} from 'extract-text-webpack-plugin';
import OfflinePlugin from 'offline-plugin';

const host = 'localhost';
const port = process.env.PORT || 3000;
const url = `http://${host}:${port}/`;

const src = 'src';
const dest = 'app';
const clean = [dest];
const copy = [
  {
    from: 'content',
  },
];

function config({dev} = {}) {
  const env = dev ? 'development' : 'production';

  return {
    devtool: dev ? 'eval-source-map' : 'hidden-source-map',
    entry: [
      ...dev ? [
        `webpack-dev-server/client?${url}`,
        'webpack/hot/only-dev-server',
        'react-hot-loader/patch',
      ] : [],
      'babel-polyfill',
      `./${src}/index.jsx`,
    ],
    output: {
      path: resolve(__dirname, dest),
      filename: `bundle${dev ? '' : '.[chunkhash]'}.js`,
    },
    module: {
      rules: [
        {
          test: /\.jsx?$/,
          include: resolve(__dirname, src),
          loader: 'babel-loader',
          options: {
            cacheDirectory: dev,
            forceEnv: env,
          },
        },
        {
          test: /\.css$/,
          include: resolve(__dirname, src),
          use: dev ? [
            'style-loader',
            {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                modules: true,
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
            },
          ] : extract({
            fallback: 'style-loader',
            use: {
              loader: 'css-loader',
              options: {
                sourceMap: true,
                modules: true,
                localIdentName: '[name]__[local]___[hash:base64:5]',
              },
            },
          }),
        },
      ],
    },
    plugins: [
      new CleanPlugin(clean),
      new CopyPlugin(copy),
      new HTMLPlugin({
        template: `${src}/template.ejs`,
        inject: false,
      }),
      new DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(env),
      }),
      ...dev ? [
        new HotModuleReplacementPlugin(),
        new NamedModulesPlugin(),
        new NoEmitOnErrorsPlugin(),
      ] : [
        // new PrepackPlugin(),
        new OptimizeJSPlugin(),
        new BabiliPlugin(),
        new ExtractTextPlugin({
          filename: `style${dev ? '' : '.[contenthash]'}.css`,
          allChunks: true,
        }),
        new OfflinePlugin(),
      ],
    ],
    resolve: {
      extensions: ['.js', '.jsx', '.json'],
    },
  };
}

export {
  config as default,
};
<|endoftext|>