<!--
title: Chat Web App
-->

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ title }}</title>
    <link href="img/favicon.png" rel="icon">
    <link href="css/default.css" rel="stylesheet" />
    {{#if debug}}<script>{{ '/js/hot-loader.js' | includeFile }}</script>{{/if}}

    <script src="js/jquery.min.js"></script>
    <script src="js/ss-utils.js"></script>
    <style>
        body {
            background-image: url(img/bg.jpg);
        }
    </style>
</head>
<body>

{{ var channels = qs.channels ?? 'home' }}
<div id="top">
    <a href="https://servicestack.net">
        <img src="https://raw.githubusercontent.com/ServiceStack/Assets/master/img/artwork/logo-32-inverted.png" style="height:28px; padding: 10px 0 0 0" />
    </a>
    <div id="social">
        <div id="welcome"></div>
        {{#if !isAuthenticated}}
        {{#each ['twitter', 'facebook', 'github']}}<a href="/auth/{{it}}" class="{{it}}"></a>{{/each}}
        {{/if}}
    </div>
    <ul id="channels" style="margin: 0 0 0 30px">
        <li style="background: none; padding: 0 0 0 5px;">
            <button onclick="var chan = prompt('Join another Channel:','ChannelName'); if (chan) location.href = '?channels={{ channels }},'+chan.replace(/\s+/g,'');">+</button>
        </li>
        <li style="background: none;padding: 0;">
            <span style="font-size: 13px; color: #ccc" onclick="$('#logs [data-channel]').html('')">clear</span>
        </li>
    </ul>
</div>
<div id="announce"></div>
<div id="tv"></div>
<div id="right">
    <div id="users"></div>
    {{#raw}}
    <div id="examples" data-click="sendCommand">
        <span style="position:absolute; top: 2px; right:7px" data-click="toggleExamples">hide</span>
        <h4><a href="https://sharpscript.net">Example #Script</a></h4>
        <div>{{ 'Cool' |&gt; padRight(7,'.') |&gt; repeat(3) }}</div>
        <div>{{#each range(17)}}{{it * it}} {{/each}}</div>
        <div>My UserAgent: {{ Request.UserAgent }}</div>
        <div>@me What's my IP? {{ Request.RemoteIP }}</div>
        <h4><a href="https://github.com/NetCoreApps/Chat#global-event-handlers">Example Commands</a></h4>
        <div>/cmd.announce This is your captain speaking ...</div>
        <div>/cmd.toggle$#channels</div>
        <h4><a href="https://github.com/NetCoreApps/Chat#modifying-css-via-jquery">CSS</a></h4>
        <div>/css.background-image url(https://bit.ly/3qho6GV)</div>
        <div>@me /css.background-image url(https://bit.ly/3qgNcWy)</div>
        <div>/css.background$#top #673ab7</div>
        <div>/css.background$#bottom #0091ea</div>
        <div>/css.background$#right #fffde7</div>
        <div>/css.color$#welcome #ff0</div>
        <h4><a href="https://github.com/NetCoreApps/Chat#receivers">Receivers</a></h4>
        <div>/tv.watch https://youtu.be/518XP8prwZo</div>
        <div>/tv.watch https://servicestack.net/img/logo-220.png</div>
        <div>@me /tv.off</div>
        <div>/document.title New Window Title</div>
    </div>
    {{/raw}}
</div>

<div id="logs"></div>
<div id="bottom">
    <input type="text" id="txtMsg" onfocus="this.value = this.value;" />
    <button id="btnSend">Send</button>
</div>

<script>
    var channels = "{{ channels }}".split(',');

    $("#channels").prepend($.map(channels, function (c) { return '<li data-click="changeChannel:' + c + '" data-channel="' + c + '">' + c + '</li>'; }));
    $("#logs").append($.map(channels, function (c) { return '<div class="channel" data-channel="' + c + '"></div>'; }));
    var selectedChannel = function () { return $("#channels .selected").html(); };

    var source = new EventSource('/api/event-stream?channels={{ channels }}&t=' + new Date().getTime()); //disable cache
    source.addEventListener('error', function (e) {
        console.log(e);
        addEntry({ msg: "ERROR!", cls: "error" });
    }, false);

    var $txtMsg = $("#txtMsg").focus(), usersMap = {}, activeSub = null;
    function refreshUsers() {
        $.getJSON("/api/event-subscribers?channels={{ channels }}", function (users) {
            usersMap = {};
            $.map(users, function (user) { usersMap[user.userId] = user; });
            $("#users").html($.map(channels, function (c) {
                var usersInChannel = $.grep($.map(usersMap,function(u){return u;}), function (u) { return u.channels.split(',').indexOf(c) >= 0; });
                return '<div class="channel" data-channel="' + c + '">'
                    + $.map(usersInChannel, function (u) { return createUser(u); }).join('')
                    + '</div>';
            }).join(''));
            $.ss.handlers["changeChannel"](selectedChannel());
        });
    }

    $.ss.eventReceivers = { "document": document };
    $(source).handleServerEvents({
        handlers: {
            onConnect: function (u) {
                console.log(u);
                activeSub = u;
                $("#welcome").html('<span>Welcome, ' + u.displayName + '</span>' + '<img src="' + u.profileUrl + '"/>');
                $("#logs .channel").html("");
                addEntry({ msg: "CONNECTED!", cls: "open" });
                $.getJSON("/api/chathistory?channels={{ channels }}", function (r) {
                    $.map(r.results, $.ss.handlers["chat"]);
                });
                refreshUsers();
            },
            onHeartbeat: function (msg, e) { if (console) console.log("onHeartbeat", msg, e); },
            onJoin: refreshUsers,
            onLeave: refreshUsers,
            onUpdate: refreshUsers,
            chat: function (m, e) {
                addEntry({ id: m.id, userId: m.fromUserId, userName: m.fromName, msg: m.message, cls: m.private ? ' private' : '', channel: m.channel || e.channel });
            }
        },
        receivers: {
            tv: {
                watch: function (id) {
                    if (id.indexOf('youtube.com') >= 0) {
                        var qs = $.ss.queryString(id);
                        $("#tv").html(templates.youtube.replace("{id}", qs["v"])).show();
                    }
                    else if (id.indexOf('youtu.be') >= 0) {
                        var v = $.ss.splitOnLast(id, '/')[1];
                        $("#tv").html(templates.youtube.replace("{id}", v)).show();
                    } else {
                        $("#tv").html(templates.generic.replace("{id}", id)).show();
                    }
                },
                off: function () {
                    $("#tv").hide().html("");
                }
            }
        }
    });
    var templates = {
        youtube: '<iframe width="640" height="360" src="//www.youtube.com/embed/{id}?autoplay=1" frameborder="0" allowfullscreen></iframe>',
        generic: '<iframe width="640" height="360" src="{id}" frameborder="0"></iframe>'
    };

    $(document).bindHandlers({
        announce: function (msg) {
            $("#announce").html(msg).fadeIn('fast');
            setTimeout(function () { $("#announce").fadeOut('slow'); }, 2000);
        },
        toggle: function () {
            $(this).toggle();
        },
        sendCommand: function () {
            $("#txtMsg").val($(this).text()).focus();
        },
        privateMsg: function () {
            $txtMsg.val("@" + this.innerHTML + " ").focus();
        },
        removeReceiver: function (name) {
            delete $.ss.eventReceivers[name];
        },
        addReceiver: function (name) {
            $.ss.eventReceivers[name] = window[name];
        },
        toggleExamples: function () {
            var willHide = this.innerHTML == "hide";
            $(this).html(willHide ? "show" : "hide").parent().css({ height: willHide ? '25px' : 'auto' });
        },
        changeChannel: function (channel) {
            $("[data-channel]").removeClass('selected');
            $("[data-channel=" + channel + "]").addClass('selected');
            $("#txtMsg").focus();
        }
    });
    $.ss.handlers["changeChannel"](channels[channels.length-1]);

    function addEntry(o) {
        console.log("addEntry", o);
        var id = "u_" + o.userId + "";
        var skipUser = $("#log .msg:last-child b").hasClass(id);
        var inactive = $(createUser($.extend(o, { displayName: o.userName }))).html();
        var user = o.userId && !skipUser ? "<b class='user " + id + "'>" + ($("#users ." + id).html() || inactive) + "</b>" : "<b class=" + id + ">&nbsp;</b>";
        var time = "<i>" + $.ss.tfmt12(new Date()) + "</i>";
        var highlight = o.msg.indexOf(activeSub.displayName.replace(" ", "")) >= 0 ? "highlight " : "";
        var filter = o.channel ? "=" + o.channel : "";
        $("#logs [data-channel" + filter + "]").append("<div id='m_" + (o.id || "0") + "' class='msg " + highlight + o.cls + "'>" +
            user + time + "<div>" + o.msg + "</div>" + "</div>").scrollTop(1E10);
    }
    function createUser(user) {
        return "<div class='user u_" + user.userId + "'><img src=\"" + (user.profileUrl || "/assets/img/no-profile64.png") + "\"/><span data-id='" +
            user.userId + "' data-click='privateMsg'>" + user.displayName + "</span></div>";
    }

    var msgHistory = [], historyIndex = -1;
    function postMsg() {
        var msg = $txtMsg.val(), parts, to = null;
        msgHistory.push(msg);

        if (msg[0] == "@") {
            parts = $.ss.splitOnFirst(msg, " ");
            var toName = parts[0].substring(1);
            if (toName == "me") {
                to = activeSub.userId;
            } else {
                var matches = $.grep($("#users .user span"),
                    function (x) { return x.innerHTML.replace(" ", "").toLowerCase() === toName.toLowerCase(); });
                to = matches.length > 0 ? matches[0].getAttribute("data-id") : null;
            }
            msg = parts[1];
        }
        if (!msg || !activeSub) return;
        var onError = function (e) {
            if (e.responseJSON && e.responseJSON.responseStatus)
                $.ss.handlers["announce"](e.responseJSON.responseStatus.message);
        };
        var channel = selectedChannel();
        if (msg[0] == "/") {
            parts = $.ss.splitOnFirst(msg, " ");
            $.post("/api/channels/" + channel + "/raw", { from: activeSub.id, toUserId: to, message: parts[1], selector: parts[0].substring(1) }, function () { }).fail(onError);
        } else {
            $.post("/api/channels/" + channel + "/chat", { from: activeSub.id, toUserId: to, message: msg, selector: "cmd.chat" }, function () { }).fail(onError);
        }
        $txtMsg.val("");
    }
    $("#btnSend").click(postMsg);

    $txtMsg.keydown(function (e) {
        var keycode = (e.keyCode ? e.keyCode : e.which);
        if ($.ss.getSelection()) {
            if (keycode === 9 || keycode === 13 || keycode === 32 || keycode === 39) {
                this.value += " ";
                if (this.setSelectionRange) this.setSelectionRange(this.value.length, this.value.length);
                return false;
            }
        }
        if (keycode === 13) { //enter
            postMsg();
        } else if (keycode === 38) { //up arrow
            historyIndex = Math.min(++historyIndex, msgHistory.length);
            $txtMsg.val(msgHistory[msgHistory.length - 1 - historyIndex]);
            return false;
        }
        else if (keycode === 40) { //down arrow
            historyIndex = Math.max(--historyIndex, -1);
            $txtMsg.val(msgHistory[msgHistory.length - 1 - historyIndex]);
        } else {
            historyIndex = -1;
        }
    }).keyup(function (e) {
        if (!$.ss.getSelection() && this.value[0] === '@' && this.value.indexOf(' ') < 0) {
            var partialVal = this.value.substring(1);
            var matchingNames = $.grep($("#users .user span")
                .map(function () { return this.innerHTML.replace(" ", ""); }), function (x) {
                return x.substring(0, partialVal.length).toLowerCase() === partialVal.toLowerCase()
                    && x.toLowerCase() !== activeSub.displayName.toLowerCase();
            });

            if (matchingNames.length > 0) {
                this.value += matchingNames[0].substring(partialVal.length);
                if (this.setSelectionRange) this.setSelectionRange(partialVal.length + 1, this.value.length);
                return false;
            }
        }
    });
</script>

{{ scripts | raw }}

</body>
</html>
