<commit_before><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>ServiceStack Redis StackOverflow</title>

    <link href="default.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>

    <script type="text/javascript">
        $(function() {
            var authUser, userStats, siteStats, qSource = "questions", qLabel = "Latest Questions";

            if (window.localStorage) {
                authUser = localStorage.getItem("authUser");
                if (typeof authUser == "string") authUser = $.parseJSON(authUser);
                if (authUser)
                {
                    loginUser(authUser.DisplayName);
                    refresh();
                }
            }

            $(document).click(function(e){
                var el = e.target;
                if (!el.getAttribute("data-cmd")) return;
                qSource = el.getAttribute("data-cmd");
                qLabel = el.getAttribute("data-label");
                refresh();
            });

            var ua = navigator.userAgent, iPhone = ua.indexOf("iPhone") >= 0, iPad = ua.indexOf("iPad") >= 0; 
            if (iPhone || iPad)
            {
                $("#footer").css({position:"static",clear:"both", margin:"80px 0 -30px -18px"});
            }

            var defaultMessage = "Browse latest or login to ask a question";

            var updateStatus = function(message, className)
            {
                var sb = className
                    ? '<b class="' + className + '">'
                    : '<b>';
                $("#status H4").html(sb + message + '</b>');
            }

            var updateUserStatus = function(message, className)
            {
                var sb = className
                    ? '<span class="' + className + '">'
                    : '<span>';
                $("#user H4").html(sb + message + '</span>');
            }

            updateStatus(defaultMessage);

            var fromDtoDate = function(dateStr) {
                return new Date(parseFloat(/Date\(([^)]+)\)/.exec(dateStr)[1]));
            } 
            var toTwitterTime = function (a) {
                var b = new Date();
                var c = typeof a == "Date" ? a : new Date(a);
                var d = b - c;
                var e = 1000, minute = e * 60, hour = minute * 60, day = hour * 24, week = day * 7;
                if (isNaN(d) || d < 0) { return "" }
                if (d < e * 7) { return "right now" }
                if (d < minute) { return Math.floor(d / e) + " secs ago" }
                if (d < minute * 2) { return "about 1 min ago" }
                if (d < hour) { return Math.floor(d / minute) + " mins ago" }
                if (d < hour * 2) { return "about 1 hour ago" }
                if (d < day) { return Math.floor(d / hour) + " hours ago" }
                if (d > day && d < day * 2) { return "yesterday" }
                if (d < day * 365) { return Math.floor(d / day) + " days ago" } else { return "over a year ago" }
            }
            function refresh(callback) {
                var asyncCount = 0;
                if (authUser)
                {
                    asyncCount++;
                    $.get("users/" + authUser.Id + "/stats", function(r){
                        userStats = r.Result;
                        var msg = "You have "
                            + "<em data-cmd='users/" + authUser.Id + "/questions' data-label='Questions by "
                                + authUser.DisplayName + "'>asked " + userStats.QuestionsCount
                            + "</em> and have answered <em>" + userStats.AnswersCount + "</em> questions";
                        updateUserStatus(msg);
                        if (--asyncCount == 0 && callback) callback();
                    });
                }
                asyncCount++;
                $.get("stats", function(r){
                    siteStats = r.Result;
                    var msg = "This site has "
                        + "<em data-cmd='questions' data-label='Latest Questions'>" + siteStats.QuestionsCount + " questions</em>"
                        + " and <em>" + siteStats.AnswersCount + "</em> answers";
                    updateStatus(msg);

                    var tags = "";
                    $.each(siteStats.TopTags, function(i,tag){
                        tags += "<dd><span class='tag' data-cmd='questions/tagged/" + tag.Name
                              + "' data-label='Questions tagged with " + tag.Name + "'>"
                              + tag.Name + "</span><strong>x" + tag.Score + "</strong></dd>";
                    });

                    var pages = "";
                    var pageCount = Math.ceil(siteStats.QuestionsCount / 10);
                    for (var pageNo=0; pageNo<pageCount; pageNo++) {
                        pages += "<span class='page' data-cmd='questions/page/" + pageNo + "'"
                               + " data-label='All Questions - page " + (pageNo+1) + "/" + pageCount + "'>"
                               + (pageNo + 1) + "</span>";
                    }
                    $("#paging").html(pages);

                    $("#toptags DL").html(tags);

                    if (--asyncCount == 0 && callback) callback();
                });

                var getTags = function(q) {
                    var tags = "";
                    $.each(q.Tags, function(i,tag){
                        tags += "<b class='tag' data-cmd='questions/tagged/" + tag
                              + "' data-label='Questions tagged with " + tag + "'>"
                              + tag + "</b>";
                    });
                    return tags;
                };

                $("#questions H2").html(qLabel);
                var encodedUrl = qSource.replace('#','%23');
                var viewSingleQuestion = qSource.indexOf('questions/') === 0 && qSource.split("/").length == 2;
                if (viewSingleQuestion) {

                    asyncCount++;
                    $.get(encodedUrl, function(r){
                        var q = r.Result.Question;
                        var votes = r.Result.VotesUpCount - r.Result.VotesDownCount;
                        var tags = getTags(q);

                        var answers = "";
                        var answerCount = r.Result.Answers.length, answerText = answerCount + " Answer";
                        if (answerCount != 1) answerText += "s";
                        $.each(r.Result.Answers, function(i, ar){
                           var u = ar.User;
                           var date = fromDtoDate(ar.Answer.CreatedDate);
                           answers += '<div class="answer">'
                                    + ar.Answer.Content.replace("\n","<br />\n")
                                    + '<div class="by">'
                                    + "<i data-cmd='users/" + u.Id + "/questions' data-label='Questions by "
                                    +  u.DisplayName + "'>by " + u.DisplayName + "</i>"
                                    +   '<em>' + toTwitterTime(date.toUTCString()) + '</em>'
                                    + '<br />'
                                    + '</div>'
                                    + '</div>'
                        });

                        var sb = ''
                            + '<div id="question">'
                                + '<div class="ib question-votes">'
                                + '<b class="up" />'
                                + '<h4>' + votes + '</h4>'
                                + '<b class="down" />'
                                + '</div>'
                            + '<div class="ib question-content">' + q.Content.replace("\n","<br />\n") + '</div>'
                            + '<div class="tags">' + tags + '</div>'
                            + '<div id="question-answers">'
                            + '<h3>' + answerText + '</h3>'
                            + answers
                            + '</div>'
                            + '<div id="your-answer">'
                            + '<h3>Your Answer</h3>'
                            + '<textarea></textarea>'
                            + '<button>Post Your Answer</button>'
                            + '</div>'
                            + '</div>';

                        $("#results").html(sb);

                        $(".question-votes B").click(function(){
                            var dir = $(this).hasClass("up") ? "up" : "down";
                            $.post("users/" + authUser.Id + "/questionvotes/" + q.Id, { Direction:dir }, function(r){
                                refresh(function(){
                                    $(".question-votes B").removeClass("user-selected");
                                    $(".question-votes B." + dir).addClass("user-selected");
                                });
                            });
                        });

                        $("#your-answer BUTTON").click(function(){
                            var answer = $("#your-answer TEXTAREA").val();

                            $.post("answers", {QuestionId: q.Id, UserId: authUser.Id, Content:answer}, function(r){
                               refresh();
                            });
                        });

                        if (--asyncCount == 0 && callback) callback();
                    });
                    return;
                }
                
                asyncCount++;
                $.get(encodedUrl, function(r){
                    var sb = "";
                    var userNameMap = {};

                    $.each(r.Results, function(i, r){
                        var q = r.Question, u = r.User;
                        var date = fromDtoDate(q.CreatedDate);

                        var aCls = r.AnswersCount > 0 ? "answered" : "unanswered";
                        var aText = r.AnswersCount == 1 ? "answer" : "answers";
                        var tags = getTags(q);

                        sb += '<div class="question">'
                            + '<dl class="votes ib"><dt>' + (r.VotesUpCount - r.VotesDownCount) + '</dt><dd>votes</dd></dl>'
                            + '<dl class="status ib ' + aCls + '"><dt>' + r.AnswersCount + '</dt><dd>' + aText + '</dd></dl>'
                            + '<div class="ib">'
                            + '<h4 data-cmd="questions/' + q.Id + '" data-label="' + q.Title + '">' + q.Title + '</h4>'
                            + '<div class="by">'
                            + "<i data-cmd='users/" + u.Id + "/questions' data-label='Questions by "
                                + u.DisplayName + "'>by " + u.DisplayName + "</i>"
                            +   '<em>' + toTwitterTime(date.toUTCString()) + '</em>'
                            + '</div>'
                            + '<div class="tags">' + tags + '</div>'
                            + '</div>'
                            + '</div>';
                    });

                    $("#results").html(sb);

                    if (--asyncCount == 0 && callback) callback();
                });
            };
            refresh();

            $("#user A#login").attr('href', 'javascript:void(0)');
            $("#user A#login").click(function() {
                var logoutUser = this.innerHTML == "logout";
                if (logoutUser) {
                    authUser = null;
                    $("#user B, #user H4, #add").hide();
                    $("#user A#login").html("login");
                    if (window.localStorage) localStorage.removeItem("authUser");
                    return;
                }
                var randUsers = "mythz,James,John,Robert,Michael,William,David,Charles,Joseph,Thomas".split(',');
                var randUser = randUsers[Math.floor(Math.random() * randUsers.length)];
                var userName = prompt("Login with your User Name", randUser);
                if (userName) {
                    loginUser(userName);
                    $.post("users", { DisplayName: userName }, function(r) {
                        authUser = r.Result;
                        if (window.localStorage) localStorage.setItem("authUser", JSON.stringify(authUser));
                        refresh();
                    });
                }
            });

            function loginUser(userName)
            {
                $("#add INPUT, #add TEXTAREA").val('');
                $("#user B, #user H4, #add").show();
                $("#user B").html("Welcome, " + userName);
                $("#user A#login").html("logout");
            }

            var addQuestion = function() {
                var title = $("#qtitle").val(), body = $("#qbody").val(), tags = $("#qtags").val();

                $("#add INPUT, #add TEXTAREA").removeClass('error');
                if (!title) $("#qtitle").addClass('error');
                if (!body)  $("#qbody").addClass('error');
                if (!tags)  $("#qtags").addClass('error');

                if (!title || !body || !tags) return;

                var dtoTags = tags.split(/[,; ]/).join(",");

                $("#add INPUT, #add TEXTAREA").val('');

                $.post("questions", { UserId: authUser.Id, Title: title, Content:body, Tags: dtoTags }, refresh);
            };
            $("#add BUTTON").click(addQuestion);


        });
    </script>

</head>
<body>
    <div id="header-links">
        <a href="../ServiceStack.Hello/">Hello World</a>
        <a href="../ServiceStack.MovieRest/">Nothing but REST!</a>
        <a href="../RedisStackOverflow/">Redis StackOverflow</a>
        <a href="../ServiceStack.Examples.Clients/">Ajax Client</a>
        <a href="../ServiceStack.Examples.Clients/Soap11.aspx">Soap 1.1</a>
        <a href="../ServiceStack.Examples.Clients/Soap12.aspx">Soap 1.2</a>
        <a href="../ServiceStack.Examples.Clients/Silverlight.htm">Silverlight</a>
    </div>

    <a class="lnk-src" href="https://github.com/mythz/ServiceStack.Examples/tree/master/src/ServiceStack.Questions"><img src="img/btn-github.png" width="186" height="84" /></a>

    <h1><a href="default.htm">Redis StackOverflow</a></h1>

    <div id="status"><h4></h4></div>

    <div id="user">
        <b></b>
        <a id="login">login</a>

        <h4></h4>
        
        <div id="add">
            <dl>
                <dd><input id="qtitle" placeholder="what is your question?" /><i>*</i></dd>
                <dd><textarea id="qbody" placeholder="body, easier to just copy one from StackOverflow"></textarea><i>*</i></dd>
                <dd><input id="qtags" placeholder="at least one tag required" /><i>*</i></dd>
                <dd><button>Ask Question</button></dd>
            </dl>
        </div>

        <div id="toptags">
            <h3>Top Tags</h3>
            <dl></dl>
        </div>

        <div id="notes">
            <h3>Application Features</h3>
            <dl>
                <dd>
                    <a href="https://github.com/mythz/ServiceStack.Examples/blob/master/src/RedisStackOverflow/RedisStackOverflow/default.htm">
                        App uses only 1 static html page</a>
                    using only jQuery
                </dd>
                <dd>
                    Uses Redis for all data access.
                    <a href="https://github.com/mythz/ServiceStack.Examples/blob/master/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs">
                        Fits in 1 C# class</a>
                </dd>
                <dd>
                    <a href="/RedisAdminUI/AjaxClient/">
                        Use RedisAdminUI to see how data is stored Redis
                    </a>
                </dd>
                <dd>
                    Uses no other external .dlls apart from
                    <a href="http://www.servicestack.net">ServiceStack</a>
                </dd>
                <dd>
                    <a href="servicestack/metadata">Checkout the complete Web Services API</a>
                </dd>
                <dd>
                    Uses only OSS software and is freely available at:
                </dd>
            </dl>

            <a id="btn-download" href="https://github.com/mythz/ServiceStack.Examples/downloads">
                <img src="http://www.servicestack.net/btn-download.gif" alt="Download ServiceStack.Examples.zip">
            </a>

        </div>
    </div>

    <div id="questions">
        <h2></h2>
        <div id="results"></div>
        <div id="paging"></div>
    </div>

    <div id="footer">
        Simple StackOverlow-like Ajax website using only <a href="http://jquery.com">jQuery</a> for UI,
        calling <a href="http://www.servicestack.net">Service Stack's</a> REST-ful web services,
        powered entirely by <a href="http://redis.io">Redis</a>.<br />

        Takes advantage of 
        <a href="http://redis.io/commands#string">STRING</a>,
        <a href="http://redis.io/commands#set">SET</a> and
        <a href="http://redis.io/commands#sorted_set">SORTED SET</a> 
        data structures in
        <a href="http://redis.io">Redis</a> 
        to provide effectively,
        the fastest implementation of a StackOverflow-like website.
    </div>

</body>
</html>
<commit_msg>Add back/forward button support using HTML5's pushstate/popstate<commit_after><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>ServiceStack Redis StackOverflow</title>

    <link href="default.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>

    <script type="text/javascript">
        $(function() {
            var authUser, userStats, siteStats, qSource = "questions", qLabel = "Latest Questions";

            if (window.localStorage) {
                authUser = localStorage.getItem("authUser");
                if (typeof authUser == "string") authUser = $.parseJSON(authUser);
                if (authUser)
                {
                    loginUser(authUser.DisplayName);
                    refresh();
                }
            }
            
            $(document).click(function(e){
                var el = e.target;
                if (!el.getAttribute("data-cmd")) return;
                qSource = el.getAttribute("data-cmd");
                qLabel = el.getAttribute("data-label");
                refresh();
            });

            var ua = navigator.userAgent, iPhone = ua.indexOf("iPhone") >= 0, iPad = ua.indexOf("iPad") >= 0; 
            if (iPhone || iPad)
            {
                $("#footer").css({position:"static",clear:"both", margin:"80px 0 -30px -18px"});
            }

            var defaultMessage = "Browse latest or login to ask a question";

            var updateStatus = function(message, className)
            {
                var sb = className
                    ? '<b class="' + className + '">'
                    : '<b>';
                $("#status H4").html(sb + message + '</b>');
            }

            var updateUserStatus = function(message, className)
            {
                var sb = className
                    ? '<span class="' + className + '">'
                    : '<span>';
                $("#user H4").html(sb + message + '</span>');
            }

            updateStatus(defaultMessage);

            var fromDtoDate = function(dateStr) {
                return new Date(parseFloat(/Date\(([^)]+)\)/.exec(dateStr)[1]));
            } 
            var toTwitterTime = function (a) {
                var b = new Date();
                var c = typeof a == "Date" ? a : new Date(a);
                var d = b - c;
                var e = 1000, minute = e * 60, hour = minute * 60, day = hour * 24, week = day * 7;
                if (isNaN(d) || d < 0) { return "" }
                if (d < e * 7) { return "right now" }
                if (d < minute) { return Math.floor(d / e) + " secs ago" }
                if (d < minute * 2) { return "about 1 min ago" }
                if (d < hour) { return Math.floor(d / minute) + " mins ago" }
                if (d < hour * 2) { return "about 1 hour ago" }
                if (d < day) { return Math.floor(d / hour) + " hours ago" }
                if (d > day && d < day * 2) { return "yesterday" }
                if (d < day * 365) { return Math.floor(d / day) + " days ago" } else { return "over a year ago" }
            }

            window.onpopstate = function(e) {
                e = e || event;
                if (!e.state) return;
                qSource = e.state.qSource;
                qLabel = e.state.qLabel;
                refresh(null, true);
            };

            function refresh(callback, skipPushState) {
                if (!skipPushState && window.history.pushState)
                    window.history.pushState({qSource:qSource,qLabel:qLabel},qLabel);

                var asyncCount = 0;
                if (authUser)
                {
                    asyncCount++;
                    $.get("users/" + authUser.Id + "/stats", function(r){
                        userStats = r.Result;
                        var msg = "You have "
                            + "<em data-cmd='users/" + authUser.Id + "/questions' data-label='Questions by "
                                + authUser.DisplayName + "'>asked " + userStats.QuestionsCount
                            + "</em> and have answered <em>" + userStats.AnswersCount + "</em> questions";
                        updateUserStatus(msg);
                        if (--asyncCount == 0 && callback) callback();
                    });
                }
                asyncCount++;
                $.get("stats", function(r){
                    siteStats = r.Result;
                    var msg = "This site has "
                        + "<em data-cmd='questions' data-label='Latest Questions'>" + siteStats.QuestionsCount + " questions</em>"
                        + " and <em>" + siteStats.AnswersCount + "</em> answers";
                    updateStatus(msg);

                    var tags = "";
                    $.each(siteStats.TopTags, function(i,tag){
                        tags += "<dd><span class='tag' data-cmd='questions/tagged/" + tag.Name
                              + "' data-label='Questions tagged with " + tag.Name + "'>"
                              + tag.Name + "</span><strong>x" + tag.Score + "</strong></dd>";
                    });

                    var pages = "";
                    var pageCount = Math.ceil(siteStats.QuestionsCount / 10);
                    for (var pageNo=0; pageNo<pageCount; pageNo++) {
                        pages += "<span class='page' data-cmd='questions/page/" + pageNo + "'"
                               + " data-label='All Questions - page " + (pageNo+1) + "/" + pageCount + "'>"
                               + (pageNo + 1) + "</span>";
                    }
                    $("#paging").html(pages);

                    $("#toptags DL").html(tags);

                    if (--asyncCount == 0 && callback) callback();
                });

                var getTags = function(q) {
                    var tags = "";
                    $.each(q.Tags, function(i,tag){
                        tags += "<b class='tag' data-cmd='questions/tagged/" + tag
                              + "' data-label='Questions tagged with " + tag + "'>"
                              + tag + "</b>";
                    });
                    return tags;
                };

                $("#questions H2").html(qLabel);
                var encodedUrl = qSource.replace('#','%23');
                var viewSingleQuestion = qSource.indexOf('questions/') === 0 && qSource.split("/").length == 2;
                if (viewSingleQuestion) {

                    asyncCount++;
                    $.get(encodedUrl, function(r){
                        var q = r.Result.Question;
                        var votes = r.Result.VotesUpCount - r.Result.VotesDownCount;
                        var tags = getTags(q);

                        var answers = "";
                        var answerCount = r.Result.Answers.length, answerText = answerCount + " Answer";
                        if (answerCount != 1) answerText += "s";
                        $.each(r.Result.Answers, function(i, ar){
                           var u = ar.User;
                           var date = fromDtoDate(ar.Answer.CreatedDate);
                           answers += '<div class="answer">'
                                    + ar.Answer.Content.replace("\n","<br />\n")
                                    + '<div class="by">'
                                    + "<i data-cmd='users/" + u.Id + "/questions' data-label='Questions by "
                                    +  u.DisplayName + "'>by " + u.DisplayName + "</i>"
                                    +   '<em>' + toTwitterTime(date.toUTCString()) + '</em>'
                                    + '<br />'
                                    + '</div>'
                                    + '</div>'
                        });

                        var sb = ''
                            + '<div id="question">'
                                + '<div class="ib question-votes">'
                                + '<b class="up" />'
                                + '<h4>' + votes + '</h4>'
                                + '<b class="down" />'
                                + '</div>'
                            + '<div class="ib question-content">' + q.Content.replace("\n","<br />\n") + '</div>'
                            + '<div class="tags">' + tags + '</div>'
                            + '<div id="question-answers">'
                            + '<h3>' + answerText + '</h3>'
                            + answers
                            + '</div>'
                            + '<div id="your-answer">'
                            + '<h3>Your Answer</h3>'
                            + '<textarea></textarea>'
                            + '<button>Post Your Answer</button>'
                            + '</div>'
                            + '</div>';

                        $("#results").html(sb);

                        $(".question-votes B").click(function(){
                            var dir = $(this).hasClass("up") ? "up" : "down";
                            $.post("users/" + authUser.Id + "/questionvotes/" + q.Id, { Direction:dir }, function(r){
                                refresh(function(){
                                    $(".question-votes B").removeClass("user-selected");
                                    $(".question-votes B." + dir).addClass("user-selected");
                                });
                            });
                        });

                        $("#your-answer BUTTON").click(function(){
                            var answer = $("#your-answer TEXTAREA").val();

                            $.post("answers", {QuestionId: q.Id, UserId: authUser.Id, Content:answer}, function(r){
                               refresh();
                            });
                        });

                        if (--asyncCount == 0 && callback) callback();
                    });
                    return;
                }
                
                asyncCount++;
                $.get(encodedUrl, function(r){
                    var sb = "";
                    var userNameMap = {};

                    $.each(r.Results, function(i, r){
                        var q = r.Question, u = r.User;
                        var date = fromDtoDate(q.CreatedDate);

                        var aCls = r.AnswersCount > 0 ? "answered" : "unanswered";
                        var aText = r.AnswersCount == 1 ? "answer" : "answers";
                        var tags = getTags(q);

                        sb += '<div class="question">'
                            + '<dl class="votes ib"><dt>' + (r.VotesUpCount - r.VotesDownCount) + '</dt><dd>votes</dd></dl>'
                            + '<dl class="status ib ' + aCls + '"><dt>' + r.AnswersCount + '</dt><dd>' + aText + '</dd></dl>'
                            + '<div class="ib">'
                            + '<h4 data-cmd="questions/' + q.Id + '" data-label="' + q.Title + '">' + q.Title + '</h4>'
                            + '<div class="by">'
                            + "<i data-cmd='users/" + u.Id + "/questions' data-label='Questions by "
                                + u.DisplayName + "'>by " + u.DisplayName + "</i>"
                            +   '<em>' + toTwitterTime(date.toUTCString()) + '</em>'
                            + '</div>'
                            + '<div class="tags">' + tags + '</div>'
                            + '</div>'
                            + '</div>';
                    });

                    $("#results").html(sb);

                    if (--asyncCount == 0 && callback) callback();
                });
            };
            refresh();

            $("#user A#login").attr('href', 'javascript:void(0)');
            $("#user A#login").click(function() {
                var logoutUser = this.innerHTML == "logout";
                if (logoutUser) {
                    authUser = null;
                    $("#user B, #user H4, #add").hide();
                    $("#user A#login").html("login");
                    if (window.localStorage) localStorage.removeItem("authUser");
                    return;
                }
                var randUsers = "mythz,James,John,Robert,Michael,William,David,Charles,Joseph,Thomas".split(',');
                var randUser = randUsers[Math.floor(Math.random() * randUsers.length)];
                var userName = prompt("Login with your User Name", randUser);
                if (userName) {
                    loginUser(userName);
                    $.post("users", { DisplayName: userName }, function(r) {
                        authUser = r.Result;
                        if (window.localStorage) localStorage.setItem("authUser", JSON.stringify(authUser));
                        refresh();
                    });
                }
            });

            function loginUser(userName)
            {
                $("#add INPUT, #add TEXTAREA").val('');
                $("#user B, #user H4, #add").show();
                $("#user B").html("Welcome, " + userName);
                $("#user A#login").html("logout");
            }

            var addQuestion = function() {
                var title = $("#qtitle").val(), body = $("#qbody").val(), tags = $("#qtags").val();

                $("#add INPUT, #add TEXTAREA").removeClass('error');
                if (!title) $("#qtitle").addClass('error');
                if (!body)  $("#qbody").addClass('error');
                if (!tags)  $("#qtags").addClass('error');

                if (!title || !body || !tags) return;

                var dtoTags = tags.split(/[,; ]/).join(",");

                $("#add INPUT, #add TEXTAREA").val('');

                $.post("questions", { UserId: authUser.Id, Title: title, Content:body, Tags: dtoTags }, refresh);
            };
            $("#add BUTTON").click(addQuestion);


        });
    </script>

</head>
<body>
    <div id="header-links">
        <a href="../ServiceStack.Hello/">Hello World</a>
        <a href="../ServiceStack.MovieRest/">Nothing but REST!</a>
        <a href="../RedisStackOverflow/">Redis StackOverflow</a>
        <a href="../ServiceStack.Examples.Clients/">Ajax Client</a>
        <a href="../ServiceStack.Examples.Clients/Soap11.aspx">Soap 1.1</a>
        <a href="../ServiceStack.Examples.Clients/Soap12.aspx">Soap 1.2</a>
        <a href="../ServiceStack.Examples.Clients/Silverlight.htm">Silverlight</a>
    </div>

    <a class="lnk-src" href="https://github.com/mythz/ServiceStack.Examples/tree/master/src/ServiceStack.Questions"><img src="img/btn-github.png" width="186" height="84" /></a>

    <h1><a href="default.htm">Redis StackOverflow</a></h1>

    <div id="status"><h4></h4></div>

    <div id="user">
        <b></b>
        <a id="login">login</a>

        <h4></h4>
        
        <div id="add">
            <dl>
                <dd><input id="qtitle" placeholder="what is your question?" /><i>*</i></dd>
                <dd><textarea id="qbody" placeholder="body, easier to just copy one from StackOverflow"></textarea><i>*</i></dd>
                <dd><input id="qtags" placeholder="at least one tag required" /><i>*</i></dd>
                <dd><button>Ask Question</button></dd>
            </dl>
        </div>

        <div id="toptags">
            <h3>Top Tags</h3>
            <dl></dl>
        </div>

        <div id="notes">
            <h3>Application Features</h3>
            <dl>
                <dd>
                    <a href="https://github.com/mythz/ServiceStack.Examples/blob/master/src/RedisStackOverflow/RedisStackOverflow/default.htm">
                        App uses only 1 static html page</a>
                    using only jQuery
                </dd>
                <dd>
                    Uses Redis for all data access.
                    <a href="https://github.com/mythz/ServiceStack.Examples/blob/master/src/RedisStackOverflow/RedisStackOverflow.ServiceInterface/IRepository.cs">
                        Fits in 1 C# class</a>
                </dd>
                <dd>
                    <a href="/RedisAdminUI/AjaxClient/">
                        Use RedisAdminUI to see how data is stored Redis
                    </a>
                </dd>
                <dd>
                    Uses no other external .dlls apart from
                    <a href="http://www.servicestack.net">ServiceStack</a>
                </dd>
                <dd>
                    <a href="servicestack/metadata">Checkout the complete Web Services API</a>
                </dd>
                <dd>
                    Uses only OSS software and is freely available at:
                </dd>
            </dl>

            <a id="btn-download" href="https://github.com/mythz/ServiceStack.Examples/downloads">
                <img src="http://www.servicestack.net/btn-download.gif" alt="Download ServiceStack.Examples.zip">
            </a>

        </div>
    </div>

    <div id="questions">
        <h2></h2>
        <div id="results"></div>
        <div id="paging"></div>
    </div>

    <div id="footer">
        Simple StackOverlow-like Ajax website using only <a href="http://jquery.com">jQuery</a> for UI,
        calling <a href="http://www.servicestack.net">Service Stack's</a> REST-ful web services,
        powered entirely by <a href="http://redis.io">Redis</a>.<br />

        Takes advantage of 
        <a href="http://redis.io/commands#string">STRING</a>,
        <a href="http://redis.io/commands#set">SET</a> and
        <a href="http://redis.io/commands#sorted_set">SORTED SET</a> 
        data structures in
        <a href="http://redis.io">Redis</a> 
        to provide effectively,
        the fastest implementation of a StackOverflow-like website.
    </div>

</body>
</html>

{'fraction_non_alphanumeric': 0.15676359039190899, 'fraction_numerical': 0.005815423514538559, 'mean_word_length': 1.220170897523857, 'pattern_counts': {'":': 0, '<': 463, '<?xml version=': 0, '>': 451, 'https://': 8, 'lorem ipsum': 0, 'www.': 10, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 54, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6642584', 'n_tokens_mistral': 9852, 'n_tokens_neox': 9087, 'n_words': 1943}