<commit_before><!DOCTYPE html>
<html>

<head>
    <title>Snipettor</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    <meta content="utf-8" http-equiv="encoding">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="<KEY>" crossorigin="anonymous">

    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="<KEY>" crossorigin="anonymous">

    <!-- Optional font awersome -->
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="<KEY>" crossorigin="anonymous">

    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput-typeahead.css" rel="stylesheet" crossorigin="anonymous">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.css" rel="stylesheet" crossorigin="anonymous">

    <script src="http://code.jquery.com/jquery.js"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="<KEY>" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.min.js"></script>
</head>

<body>

    <nav class="navbar navbar-default" role="navigation">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Snipettor</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a href="#"><i class="fa fa-github fa-2x"></i></a></li>
                <li><a href="#"><i class="fa fa-google fa-2x"></i></a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">Working snippets <b class="caret"></b></a>
                    <ul class="dropdown-menu" id="snippetor-select-menu" >
                        <li class="snippet-draft"><a href="#">Snippet 1</a></li>
                        <li class="snippet-draft"><a href="#">Snippet 2</a></li>
                        <li class="snippet-draft"><a href="#">Snippet 3</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Create new</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Fork snippet</a></li>
                    </ul>
                </li>
            </ul>
            <div class="col-sm-3 col-md-3">
                <form class="navbar-form" role="search">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="Search snippet" name="q">
                        <div class="input-group-btn">
                            <button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
                        </div>
                    </div>
                </form>
            </div>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Settings</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">Sign In <b class="caret"></b></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Google</a></li>
                        <li><a href="#">GitHub</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </nav>

    <div class="container-fluid">
        <div class="row">
            <div class="col-sm-1 col-md-2 col-lg-3">
            </div>
            <div id="main-content" class="col-sm-10 col-md-8 col-lg-6">
            </div>
            <div class="col-sm-1 col-md-2 col-lg-3">
            </div>
        </div>
    </div>

    <script src="https://www.gstatic.com/firebasejs/3.6.7/firebase.js"></script>
    <script>
        // Initialize Firebase
        var config = {
            apiKey: "<KEY>",
            authDomain: "snipettor.firebaseapp.com",
            databaseURL: "https://snipettor.firebaseio.com",
            storageBucket: "snipettor.appspot.com",
            messagingSenderId: "1024573617684"
        };
        firebase.initializeApp(config);
    </script>

    <script>
        var global_action = null;
        var uiHandler = {};
        
        function showContentItem(item, index, extApi) {
                    var github_prefix = "https://github.com/";
                    var url_path = item.url.substr(github_prefix.length);

                    var user = url_path.substr(0, url_path.indexOf("/"));
                    url_path = url_path.substr(url_path.indexOf("/") + 1);

                    var repo = url_path.substr(0, url_path.indexOf("/"));
                    url_path = url_path.substr(url_path.indexOf("/") + 1);

                    var panelTemplate =
                        '\
<div class="panel panel-default">\
    <i class="fa fa-chevron-down fa-3x" style="float: left;position: relative;left: -50px;top: 70px;"></i>\
    <i class="fa fa-chevron-up fa-3x" style="float: left;left: -93px;position: relative;top: 20px;"></i>\
<div class="panel-heading" style="background-image: none;">\
<i class="fa fa-close" style="position:absolute; right: 80px;"></i>\
  <h3 class="panel-title">\
  <ul class="breadcrumb" style="margin-bottom: 0px;">\
      <li><a href="https://github.com"><p><i class="fa fa-github"></i></p></a></li>\
      <li><a href="#">' +
                        user + '</a> <span class="divider"></span></li>\
      <li><a href="#">' + repo + '</a> <span class="divider"></span></li>\
      <li><a href="#">' + url_path +
                        '</a> <span class="divider"></span></li>\
      <li><a href="#">L#' + item.line +
                        '</a></li>\
    </ul>\
  </h3>\
</div>\
<div class="panel-body">  <p style="position:relative; float:right;"><i class="fa fa-edit"></i></p><span class="snippet-description">' +
                        item.comment + '</span></div>\
</div>';
                    //
                    var item = $(panelTemplate).appendTo("#snipettor-save-form");

                    // Sort the comment items
                    item.find(".fa-chevron-up").click(function() {
                        var oldIndex = $(".panel-default").index($(this).parent());
                        if (oldIndex > 0) {
  						  var payload = {oldIndex: oldIndex, newIndex:oldIndex-1};
                          extApi.updateSnippetItem("move", payload);
                          uiHandler.doSwapItems(payload);
                        }
                    });
                    item.find(".fa-chevron-down").click(function() {
                        var oldIndex = $(".panel-default").index($(this).parent());
                        if (oldIndex < $(".panel-default").length-1) {
						  var  payload = {oldIndex: oldIndex, newIndex:oldIndex+1};
                          extApi.updateSnippetItem("move", payload);
                          uiHandler.doSwapItems(payload);
                        }
                    });
                    item.find(".fa-close").click(function() {
                        extApi.updateSnippetItem("delete", {index: 0});
                        var parent = $(this).parent().parent();
                        parent.remove();
                    });
                    // edit the text of comment
                    item.find(".fa-edit").click(function() {
                        var parent = $(this).parent().parent();

                        if (parent.find("textarea").length > 0) {
                            parent.find("textarea").trigger("blur");
                            return;
                        }
                          
                        var text = parent.find(".snippet-description").html();
                        parent.find(".snippet-description").hide();
                        parent.append("<textarea style='width:100%;'>"+text+"</textarea>");
                        parent.find("textarea").blur(function() {
                            var xxx = parent.find("textarea").val();
                            parent.find("textarea").remove();
                            if (xxx != text) {
                              parent.find(".snippet-description").text(xxx);
                              extApi.updateSnippetItem("update", {comment: xxx});
                            }
                            parent.find(".snippet-description").show();
                        });
                    });
            };

        var storageApi = {
            init: function() {
                // make firebase initialization here
                this.auth = firebase.auth();
                this.database = firebase.database();
                this.storage = firebase.storage();
                // Initiates Firebase auth and listen to auth state changes.
                this.auth.onAuthStateChanged(this._onAuthStateChanged.bind(this));
            },
            //
            // Helper method to handle sign-in change behaviour
            //
            _onAuthStateChanged: function(user) {
                this.user = user;
                if (this.authCallback) {
                    // probably it could trigger firebase:auth event
                    this.authCallback(user);
                }
            },
            //
            // Helper method to hide storage selection
            // for the authenticated and anonimous users
            //
            _getMessageRef: function(type) {
                if (type == 'content') {
                    if (!this.umlsRef) {
                        this.umlsRef = this.database.ref('messages');
                    }
                    return this.umlsRef;
                }
            },
            //
            // Save a new content
            //
            saveContent: function(payload, callback) {
                var messagesRef = this._getMessageRef('content');
                if (!messagesRef)
                    callback(null);

                var currentUser = this.auth.currentUser;
                messagesRef.push({
                    version: 0, // default is 0
                    name: currentUser ? currentUser.displayName : "unknown",
                    title: payload.title, // UML title
                    tags: payload.tags || [], // uml/class etc or markdown
                    description: payload.description || "", // uml/class etc or markdown
                    items: payload.items, // JSON description
                }).then(function(snapshot) {
                    if (callback) {
                        callback(snapshot.path);
                    }
                }.bind(this)).catch(function(error) {
                    var text = 'Error writing new message to Firebase Database';
                    console.error(text, error);
                    callback(null, text);
                });

            },
            //
            // Load content by key and version
            //
            loadContent: function(uid, version, callback) {
                var that = this;
                var handler = function(snapshot) {
                    if (callback)
                        callback(snapshot.val());
                    var xxx = snapshot.val();
                    console.dir(xxx);
                };
                var handler2 = function(snapshot) {
                    if (callback) {
                        var xxx = snapshot.val();
                        for (var x in xxx) {
                            console.log(x);
                            console.dir(xxx[x]);
                            callback(xxx[x]);
                        }
                    }
                };

                if (version) {
                    console.log("LOADING: window/" + uid + "/" + version);
                    var ref = this.database.ref('messages/' + uid + "/" + version);
                    ref.limitToFirst(1).once("value").then(handler2);
                } else {
                    this.messagesRef = this.database.ref('messages/' + uid).once('value').then(handler);
                }
            },
            //
            // Update the content and version
            //
            updateContent: function(uid, payload, callback) {
              var msg = this._getMessageRef('content');
              if (msg) {
                var ref = msg.child("-" +uid);
                ref.child('version').once('value').then(function(x) {
                  var position = x.val()+1;
                  ref
                  .child(position)
                  .push({data: payload.data, title: payload.title, type: payload.type})
                  .then(function(snapshot) {
                      if (callback) {
                        callback(snapshot.path);
                      }
                      ref.update({version: position});
                  }.bind(this))
                  .catch(function(error) {
                      var text = 'Error updating existing content to Firebase Database';
                      console.error(text, error);
                      callback(null, text);
                  });
                }); // ref get value
              }
            },

            //
            // Show the latest content only
            //
            searchContent: function(queryText, callback) {
              if (queryText) {
                this.database.ref('messages')
                .limitToLast(20)
                .orderByChild('title')
                .startAt(queryText)
                .endAt(queryText+"\uf8ff")
                .once('value')
                .then(function(complete) {
                    callback(complete.val());
                });
              } else {
                this.database.ref('messages')
                .limitToLast(20)
                .once('value')
                .then(function(complete) {
                    callback(complete.val());
                });
              }
            }
        }; // StorageApi

        var extensionRemoteApi = {
            handlers: null,
            openSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "open-snippet",
                  data: payload
                }
              }));
            },
            onSavedSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "saved-draft",
                  payload: payload
                }
              }));

            },
            onUnsubscribeSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "unsubscribe",
                  payload: payload
                }
              }));

            },
            init: function(handlers) {
              extensionRemoteApi.handlers = handlers;

        window.addEventListener("onInit", function(evt) {
            if (extensionRemoteApi.handlers 
                && extensionRemoteApi.handlers.onInit)
           extensionRemoteApi.handlers.onInit(evt.detail);
        });
        window.addEventListener("onSnippetChange", function(evt) {
            var payload = evt.detail;
            if (payload.action == "save") {
                extensionRemoteApi.handlers.onSaveSnippet(payload);
            } else if (payload.action == "create") {
                extensionRemoteApi.handlers.onCreateSnippet(payload);
            } else if (payload.action == "open") {
                extensionRemoteApi.handlers.onOpenSnippet(payload);
            } else {
                alert("Unknow snippet action: " + payload.action);
            }
        });

        window.addEventListener("onSnippetItemChange", function(evt) {
            var payload = evt.detail;
            if (payload.action == "add") {
                extensionRemoteApi.handlers.onAddItem(payload);
            } else if (payload.action == "remove") {
                extensionRemoteApi.handlers.onRemoveItem(payload);
            } else if (payload.action == "update") {
                extensionRemoteApi.handlers.onUpdateItem(payload);
            } else if (payload.action == "move") {
                extensionRemoteApi.handlers.onMoveItem(payload);
            } else {
                alert("Unknow snippet action: " + payload.action);
            }
        });


              setTimeout(function() {
                window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                  detail: {
                    action: "GetInitialState"
                  }
                 }));
             }, 100);
            },
            //
            // Assign current tab to the concreate snippet or snippet draft
            // @param payload - index of the selected item, (-1) is to unsubscribe
            //
            selectSnippet: function(index) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "select-snippet",
                  payload: index
                }
              }));
            },
            //
            // Update current snippet data:
            // title, hash-tags or description 
            //
            updateSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "update-snippet",
                  payload: payload
                }
              }));
            },
            //
            // Update the concreate snippet item's description
            // 1. update - update description
            // 2. delete - item removed
            // 3. position - change item position
            //
            updateSnippetItem: function(action, payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: action + "-snippet-item",
                  payload: payload
                }
              }));
            }
        };
        
        var handlers = {
            initialized: false,
               onInit: function(response) {
              console.log(">onInit");
              console.dir(response);

              uiHandler.updateWorkingSnippets(response.snippets);
              if (response.working != undefined)  {
                uiHandler.handleWorkingSnippet(response.working);
              }
              else {
                  if (global_action == "SAVE")
                    alert("There is nothing to save! Please, select some snippet");
              }

              handlers.initialized = true;
            },
            onSaveSnippet: function(payload) {
                console.log("7");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    uiHandler.onSaveCurrentSnippet(payload.working);
                }
                // send unsubscribe from the current snippet
                extensionRemoteApi.onUnsubscribeSnippet(payload.working);
                setTimeout(function() {
                    window.location.href = "/";
                }, 100);

                uiHandler.snippets[payload.working] = null;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);

            },
            onCreateSnippet: function(payload){
                console.log("6");
                if (!handlers.initialized)
                  return;

                uiHandler.snippets[payload.working] = payload.snippet;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);
            },
            onOpenSnippet: function(payload) {
                console.log("5");
                if (!handlers.initialized)
                  return;

                uiHandler.snippets[payload.working] = payload.snippet;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);
            },
            onAddItem: function(payload) {
                console.log("4");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    showContentItem(payload.item, payload.index, extensionRemoteApi);
                }
                uiHandler.snippets[payload.working].items.splice(payload.index, 0, payload.item);
            },
            onRemoveItem: function(payload) {
                console.log("3");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    removeContentItem(payload.index);
                }
                uiHandler.snippets[payload.working].items.splice(payload.index, 1);
            },
            onUpdateItem: function(payload) {
                console.log("2");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    console.log("Update UI description only");
                }
                uiHandler.snippets[payload.working].items[payload.index] = payload.item;
            },
            onMoveItem: function(payload) {
                console.log("on swap: " + payload.working + " == " + uiHandler.working);
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    uiHandler.doSwapItems(payload.payload);
                }
                var item = uiHandler.snippets[payload.working].items[payload.payload.oldIndex];
                console.dir(item);
                uiHandler.snippets[payload.working].items.splice(payload.payload.oldIndex, 1);
                uiHandler.snippets[payload.working].items.splice(payload.payload.newIndex, 0, item);
            }
        };

        var container = null;
        $(document).ready(function() {
            var content = window.location.href.split("?");
            //
            // initialize firebase auth provider
            //
            storageApi.init();
            
            extensionRemoteApi.init(handlers);

            if (content.length > 1) {
                content = content[1];
                if (content.indexOf("save") >= 0) {
                    global_action = "SAVE";
                    //showContent(container);
                } // save
                else if (content.indexOf("open") >= 0) {
                    var content = content.substr(5);
                    var uid = content.substr(0, content.length - 1);
                    storageApi.loadContent("-" + uid, null, function(data) {
                        console.dir(data);
                        if (data) data.uid = uid;
                        showContent(data);
                    });
                }
                else if (content.indexOf("search") >= 0) {
                    var content = content.substr(7, content.length - 8);
                    storageApi.searchContent(content, function(content) {
                      showSearchResult(content);
                    });
                }

            } else { // content is not a query
                storageApi.searchContent(null, function(content) {
                    showSearchResult(content);
                });
            }

            function addSaveForm(payload) {
                // remove the previous form and create a new one
                $("#snipettor-save-form").remove();
                $('<form id="snipettor-save-form" action="#" uid="' + (payload.uid || '')+ '" class="navbar-form pull-left">\
    <button type="button" class="btn"><i class="fa fa-play">&nbsp;Play</i></button>&nbsp;&nbsp;&nbsp;\
    '+ (payload.uid ? '': '<button type="submit" class="btn"><i class="fa fa-delete">&nbsp;Delete</i></button>&nbsp;&nbsp;&nbsp;') + '\
    <button type="submit" class="btn"><i class="fa fa-save">&nbsp;Save</i></button>&nbsp;&nbsp;&nbsp;\
    '+ (payload.uid ? '<button type="submit" class="btn"><i class="fa fa-fork">&nbsp;Fork</i></button>': '')+ ' <br><br>\
  <div class="raw">\
        <label for="title">Title*    </label><br>\
        <input id="snippetor-save-title" class="form-control input-sm" type="text" class="span2" style="width: 550px;" value="' +
                    payload.title +
                    '"><br>\
    </div><br>\
    <div class="raw">\
        <label for="tags">Tags*</label><br>\
        <input id="snippetor-save-tags" type="text" value="'+( payload.tags) +'" class="form-control input-sm" data-role="tagsinput" class="span2" style="min-width: 550px;">\
    </div>\
    <div class="raw">\
        <label for="description">Description*</label><br>\
        <textarea id="snippetor-save-description" class="form-control input-sm" id="snippetor-save-description" rows="3" style="min-width: 550px;">'+payload.description+'</textarea>\
    </div>\
    <br>\
  </form>'
                ).appendTo("#main-content");
            }

            function showSearchResult(searchData) {
                for (var x in searchData) {
                    var description = searchData[x];
                    var xxx = x.substr(1);
                    $('<div class="container">\
<h2 ><div class="input-group" style="display:flex;"><a class="snippetor-play-snippet" uid="'+x+'"><p><i class="fa fa-play">Play</i></p></a> &nbsp;&nbsp;/&nbsp;&nbsp; <a class="snippetor-detail-snippet" href="/?open='+xxx+'"><p><i class="fa fa-edit">&nbsp;Details</i></p></a></div></h2>\
<div class="panel panel-default">\
<div class="panel-heading">' + description.title + '</div>\
<div class="panel-body">' + description.description +
                        '</div>\
</div>\
</div>').prependTo("#main-content");
                }
                $(".snippetor-play-snippet").click(function(){
                  var uid = $(this).attr("uid");
                  var payload = searchData[uid];
                  if (!uid)
                    return;

                  uid = uid.substr(1);
                  payload.uid = uid; // save current unique ID

                  extensionRemoteApi.openSnippet(payload);
                });
            }


            function showContent(payload) {
                
                addSaveForm(payload);
                for (var r = 0; r < payload.items.length; ++r) {
                    showContentItem(payload.items[r], null, extensionRemoteApi);

                } // for

                //
                // Save content to the firebase storage
                //
                $("#snipettor-save-form").submit(function(e) {

                  e.preventDefault();
                  e.stopPropagation();
                    
                  // Update title tags and description
                  payload.title = $("#snippetor-save-title").val();
                  payload.tags = $("#snippetor-save-tags").val();
                  payload.description = $("#snippetor-save-description").val();
                  
                  var uid = $(this).attr("uid");
                  if (!uid || uid == "") {
                    storageApi.saveContent(payload, function(path) {
                        var uniquePath = path.toString().split("/")[2];
                        uniquePath = uniquePath.substr(1);
                        // handle snippet save
                        extensionRemoteApi.onSavedSnippet({uid: uniquePath});
                        
                        setTimeout(function() {
                          window.location.href = "/?open=" + uniquePath;
                        }, 100);
                    });
                  } else {
                    storageApi.updateContent(uid, payload, function(path) {
                        var uniquePath = path.toString().split("/")[2];
                        uniquePath = uniquePath.substr(1);

                        // handle snippet saved
                        extensionRemoteApi.onSavedSnippet({uid: uniquePath});

                        setTimeout(function() {
                          window.location.href = "/?open=" + uniquePath;
                        }, 100);
                     }); // update content API
                }
                return false;
              }); // on sumbit method
            } // show Content method

         uiHandler = {
             snippets: [],
             working: -1,
             updateWorkingSnippets: function(snippets) {
                 $("ul#snippetor-select-menu>li.snippet-draft").remove();
                 for (var i in snippets) {
                     var sn = snippets[i];
                     if (sn)
                       $("ul#snippetor-select-menu").prepend('<li class="snippet-draft"><a href="#">'+sn.title+'</a></li>');
                 }
                 uiHandler.snippets = snippets;
             },
             handleWorkingSnippet: function(id) {
                 if (uiHandler.snippets.length > id) {
                     uiHandler.working = id;
                     if (global_action == "SAVE")
                       showContent(uiHandler.snippets[id]);
                 }
             },
             onSaveCurrentSnippet: function() {
                 window.location.href = "/";
             },
             doSwapItems: function(action) {
                 if (action.oldIndex == action.newIndex)
                   return;

                 var lis = $('div.panel-default');
                 console.dir(action);
                 console.dir(lis);
                 var ttt = lis.eq(action.oldIndex);
                 var new_ttt = lis.eq(action.newIndex);
                 console.dir(ttt);
                 if (action.newIndex < action.oldIndex) {
                   new_ttt.before(ttt);
                 } else {
                   new_ttt.after(ttt);
                 }
             }
         };


        }); // document ready
    </script>

</body>

</html>
<commit_msg>Replace undefined on empty value<commit_after><!DOCTYPE html>
<html>

<head>
    <title>Snipettor</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    <meta content="utf-8" http-equiv="encoding">
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="<KEY>" crossorigin="anonymous">

    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="<KEY>" crossorigin="anonymous">

    <!-- Optional font awersome -->
    <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="<KEY>NEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">

    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput-typeahead.css" rel="stylesheet" crossorigin="anonymous">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.css" rel="stylesheet" crossorigin="anonymous">

    <script src="http://code.jquery.com/jquery.js"></script>
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="<KEY>" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-tagsinput/0.8.0/bootstrap-tagsinput.min.js"></script>
</head>

<body>

    <nav class="navbar navbar-default" role="navigation">
        <!-- Brand and toggle get grouped for better mobile display -->
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Snipettor</a>
        </div>

        <!-- Collect the nav links, forms, and other content for toggling -->
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a href="#"><i class="fa fa-github fa-2x"></i></a></li>
                <li><a href="#"><i class="fa fa-google fa-2x"></i></a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">Working snippets <b class="caret"></b></a>
                    <ul class="dropdown-menu" id="snippetor-select-menu" >
                        <li class="snippet-draft"><a href="#">Snippet 1</a></li>
                        <li class="snippet-draft"><a href="#">Snippet 2</a></li>
                        <li class="snippet-draft"><a href="#">Snippet 3</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Create new</a></li>
                        <li class="divider"></li>
                        <li><a href="#">Fork snippet</a></li>
                    </ul>
                </li>
            </ul>
            <div class="col-sm-3 col-md-3">
                <form class="navbar-form" role="search">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="Search snippet" name="q">
                        <div class="input-group-btn">
                            <button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
                        </div>
                    </div>
                </form>
            </div>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">Settings</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown">Sign In <b class="caret"></b></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">Google</a></li>
                        <li><a href="#">GitHub</a></li>
                    </ul>
                </li>
            </ul>
        </div>
        <!-- /.navbar-collapse -->
    </nav>

    <div class="container-fluid">
        <div class="row">
            <div class="col-sm-1 col-md-2 col-lg-3">
            </div>
            <div id="main-content" class="col-sm-10 col-md-8 col-lg-6">
            </div>
            <div class="col-sm-1 col-md-2 col-lg-3">
            </div>
        </div>
    </div>

    <script src="https://www.gstatic.com/firebasejs/3.6.7/firebase.js"></script>
    <script>
        // Initialize Firebase
        var config = {
            apiKey: "<KEY>",
            authDomain: "snipettor.firebaseapp.com",
            databaseURL: "https://snipettor.firebaseio.com",
            storageBucket: "snipettor.appspot.com",
            messagingSenderId: "1024573617684"
        };
        firebase.initializeApp(config);
    </script>

    <script>
        var global_action = null;
        var uiHandler = {};
        
        function showContentItem(item, index, extApi) {
                    var github_prefix = "https://github.com/";
                    var url_path = item.url.substr(github_prefix.length);

                    var user = url_path.substr(0, url_path.indexOf("/"));
                    url_path = url_path.substr(url_path.indexOf("/") + 1);

                    var repo = url_path.substr(0, url_path.indexOf("/"));
                    url_path = url_path.substr(url_path.indexOf("/") + 1);

                    var panelTemplate =
                        '\
<div class="panel panel-default">\
    <i class="fa fa-chevron-down fa-3x" style="float: left;position: relative;left: -50px;top: 70px;"></i>\
    <i class="fa fa-chevron-up fa-3x" style="float: left;left: -93px;position: relative;top: 20px;"></i>\
<div class="panel-heading" style="background-image: none;">\
<i class="fa fa-close" style="position:absolute; right: 80px;"></i>\
  <h3 class="panel-title">\
  <ul class="breadcrumb" style="margin-bottom: 0px;">\
      <li><a href="https://github.com"><p><i class="fa fa-github"></i></p></a></li>\
      <li><a href="#">' +
                        user + '</a> <span class="divider"></span></li>\
      <li><a href="#">' + repo + '</a> <span class="divider"></span></li>\
      <li><a href="#">' + url_path +
                        '</a> <span class="divider"></span></li>\
      <li><a href="#">L#' + item.line +
                        '</a></li>\
    </ul>\
  </h3>\
</div>\
<div class="panel-body">  <p style="position:relative; float:right;"><i class="fa fa-edit"></i></p><span class="snippet-description">' +
                        item.comment + '</span></div>\
</div>';
                    //
                    var item = $(panelTemplate).appendTo("#snipettor-save-form");

                    // Sort the comment items
                    item.find(".fa-chevron-up").click(function() {
                        var oldIndex = $(".panel-default").index($(this).parent());
                        if (oldIndex > 0) {
  						  var payload = {oldIndex: oldIndex, newIndex:oldIndex-1};
                          extApi.updateSnippetItem("move", payload);
                          uiHandler.doSwapItems(payload);
                        }
                    });
                    item.find(".fa-chevron-down").click(function() {
                        var oldIndex = $(".panel-default").index($(this).parent());
                        if (oldIndex < $(".panel-default").length-1) {
						  var  payload = {oldIndex: oldIndex, newIndex:oldIndex+1};
                          extApi.updateSnippetItem("move", payload);
                          uiHandler.doSwapItems(payload);
                        }
                    });
                    item.find(".fa-close").click(function() {
                        extApi.updateSnippetItem("delete", {index: 0});
                        var parent = $(this).parent().parent();
                        parent.remove();
                    });
                    // edit the text of comment
                    item.find(".fa-edit").click(function() {
                        var parent = $(this).parent().parent();

                        if (parent.find("textarea").length > 0) {
                            parent.find("textarea").trigger("blur");
                            return;
                        }
                          
                        var text = parent.find(".snippet-description").html();
                        parent.find(".snippet-description").hide();
                        parent.append("<textarea style='width:100%;'>"+text+"</textarea>");
                        parent.find("textarea").blur(function() {
                            var xxx = parent.find("textarea").val();
                            parent.find("textarea").remove();
                            if (xxx != text) {
                              parent.find(".snippet-description").text(xxx);
                              extApi.updateSnippetItem("update", {comment: xxx});
                            }
                            parent.find(".snippet-description").show();
                        });
                    });
            };

        var storageApi = {
            init: function() {
                // make firebase initialization here
                this.auth = firebase.auth();
                this.database = firebase.database();
                this.storage = firebase.storage();
                // Initiates Firebase auth and listen to auth state changes.
                this.auth.onAuthStateChanged(this._onAuthStateChanged.bind(this));
            },
            //
            // Helper method to handle sign-in change behaviour
            //
            _onAuthStateChanged: function(user) {
                this.user = user;
                if (this.authCallback) {
                    // probably it could trigger firebase:auth event
                    this.authCallback(user);
                }
            },
            //
            // Helper method to hide storage selection
            // for the authenticated and anonimous users
            //
            _getMessageRef: function(type) {
                if (type == 'content') {
                    if (!this.umlsRef) {
                        this.umlsRef = this.database.ref('messages');
                    }
                    return this.umlsRef;
                }
            },
            //
            // Save a new content
            //
            saveContent: function(payload, callback) {
                var messagesRef = this._getMessageRef('content');
                if (!messagesRef)
                    callback(null);

                var currentUser = this.auth.currentUser;
                messagesRef.push({
                    version: 0, // default is 0
                    name: currentUser ? currentUser.displayName : "unknown",
                    title: payload.title, // UML title
                    tags: payload.tags || [], // uml/class etc or markdown
                    description: payload.description || "", // uml/class etc or markdown
                    items: payload.items, // JSON description
                }).then(function(snapshot) {
                    if (callback) {
                        callback(snapshot.path);
                    }
                }.bind(this)).catch(function(error) {
                    var text = 'Error writing new message to Firebase Database';
                    console.error(text, error);
                    callback(null, text);
                });

            },
            //
            // Load content by key and version
            //
            loadContent: function(uid, version, callback) {
                var that = this;
                var handler = function(snapshot) {
                    if (callback)
                        callback(snapshot.val());
                    var xxx = snapshot.val();
                    console.dir(xxx);
                };
                var handler2 = function(snapshot) {
                    if (callback) {
                        var xxx = snapshot.val();
                        for (var x in xxx) {
                            console.log(x);
                            console.dir(xxx[x]);
                            callback(xxx[x]);
                        }
                    }
                };

                if (version) {
                    console.log("LOADING: window/" + uid + "/" + version);
                    var ref = this.database.ref('messages/' + uid + "/" + version);
                    ref.limitToFirst(1).once("value").then(handler2);
                } else {
                    this.messagesRef = this.database.ref('messages/' + uid).once('value').then(handler);
                }
            },
            //
            // Update the content and version
            //
            updateContent: function(uid, payload, callback) {
              var msg = this._getMessageRef('content');
              if (msg) {
                var ref = msg.child("-" +uid);
                ref.child('version').once('value').then(function(x) {
                  var position = x.val()+1;
                  ref
                  .child(position)
                  .push({data: payload.data, title: payload.title, type: payload.type})
                  .then(function(snapshot) {
                      if (callback) {
                        callback(snapshot.path);
                      }
                      ref.update({version: position});
                  }.bind(this))
                  .catch(function(error) {
                      var text = 'Error updating existing content to Firebase Database';
                      console.error(text, error);
                      callback(null, text);
                  });
                }); // ref get value
              }
            },

            //
            // Show the latest content only
            //
            searchContent: function(queryText, callback) {
              if (queryText) {
                this.database.ref('messages')
                .limitToLast(20)
                .orderByChild('title')
                .startAt(queryText)
                .endAt(queryText+"\uf8ff")
                .once('value')
                .then(function(complete) {
                    callback(complete.val());
                });
              } else {
                this.database.ref('messages')
                .limitToLast(20)
                .once('value')
                .then(function(complete) {
                    callback(complete.val());
                });
              }
            }
        }; // StorageApi

        var extensionRemoteApi = {
            handlers: null,
            openSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "open-snippet",
                  data: payload
                }
              }));
            },
            onSavedSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "saved-draft",
                  payload: payload
                }
              }));

            },
            onUnsubscribeSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "unsubscribe",
                  payload: payload
                }
              }));

            },
            init: function(handlers) {
              extensionRemoteApi.handlers = handlers;

        window.addEventListener("onInit", function(evt) {
            if (extensionRemoteApi.handlers 
                && extensionRemoteApi.handlers.onInit)
           extensionRemoteApi.handlers.onInit(evt.detail);
        });
        window.addEventListener("onSnippetChange", function(evt) {
            var payload = evt.detail;
            if (payload.action == "save") {
                extensionRemoteApi.handlers.onSaveSnippet(payload);
            } else if (payload.action == "create") {
                extensionRemoteApi.handlers.onCreateSnippet(payload);
            } else if (payload.action == "open") {
                extensionRemoteApi.handlers.onOpenSnippet(payload);
            } else {
                alert("Unknow snippet action: " + payload.action);
            }
        });

        window.addEventListener("onSnippetItemChange", function(evt) {
            var payload = evt.detail;
            if (payload.action == "add") {
                extensionRemoteApi.handlers.onAddItem(payload);
            } else if (payload.action == "remove") {
                extensionRemoteApi.handlers.onRemoveItem(payload);
            } else if (payload.action == "update") {
                extensionRemoteApi.handlers.onUpdateItem(payload);
            } else if (payload.action == "move") {
                extensionRemoteApi.handlers.onMoveItem(payload);
            } else {
                alert("Unknow snippet action: " + payload.action);
            }
        });


              setTimeout(function() {
                window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                  detail: {
                    action: "GetInitialState"
                  }
                 }));
             }, 100);
            },
            //
            // Assign current tab to the concreate snippet or snippet draft
            // @param payload - index of the selected item, (-1) is to unsubscribe
            //
            selectSnippet: function(index) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "select-snippet",
                  payload: index
                }
              }));
            },
            //
            // Update current snippet data:
            // title, hash-tags or description 
            //
            updateSnippet: function(payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: "update-snippet",
                  payload: payload
                }
              }));
            },
            //
            // Update the concreate snippet item's description
            // 1. update - update description
            // 2. delete - item removed
            // 3. position - change item position
            //
            updateSnippetItem: function(action, payload) {
              window.dispatchEvent(new CustomEvent("onSnipettorAction", {
                detail: {
                  action: action + "-snippet-item",
                  payload: payload
                }
              }));
            }
        };
        
        var handlers = {
            initialized: false,
               onInit: function(response) {
              console.log(">onInit");
              console.dir(response);

              uiHandler.updateWorkingSnippets(response.snippets);
              if (response.working != undefined)  {
                uiHandler.handleWorkingSnippet(response.working);
              }
              else {
                  if (global_action == "SAVE")
                    alert("There is nothing to save! Please, select some snippet");
              }

              handlers.initialized = true;
            },
            onSaveSnippet: function(payload) {
                console.log("7");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    uiHandler.onSaveCurrentSnippet(payload.working);
                }
                // send unsubscribe from the current snippet
                extensionRemoteApi.onUnsubscribeSnippet(payload.working);
                setTimeout(function() {
                    window.location.href = "/";
                }, 100);

                uiHandler.snippets[payload.working] = null;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);

            },
            onCreateSnippet: function(payload){
                console.log("6");
                if (!handlers.initialized)
                  return;

                uiHandler.snippets[payload.working] = payload.snippet;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);
            },
            onOpenSnippet: function(payload) {
                console.log("5");
                if (!handlers.initialized)
                  return;

                uiHandler.snippets[payload.working] = payload.snippet;
                uiHandler.updateWorkingSnippets(uiHandler.snippets);
            },
            onAddItem: function(payload) {
                console.log("4");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    showContentItem(payload.item, payload.index, extensionRemoteApi);
                }
                uiHandler.snippets[payload.working].items.splice(payload.index, 0, payload.item);
            },
            onRemoveItem: function(payload) {
                console.log("3");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    removeContentItem(payload.index);
                }
                uiHandler.snippets[payload.working].items.splice(payload.index, 1);
            },
            onUpdateItem: function(payload) {
                console.log("2");
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    console.log("Update UI description only");
                }
                uiHandler.snippets[payload.working].items[payload.index] = payload.item;
            },
            onMoveItem: function(payload) {
                console.log("on swap: " + payload.working + " == " + uiHandler.working);
                if (!handlers.initialized)
                  return;

                if (payload.working == uiHandler.working) {
                    uiHandler.doSwapItems(payload.payload);
                }
                var item = uiHandler.snippets[payload.working].items[payload.payload.oldIndex];
                console.dir(item);
                uiHandler.snippets[payload.working].items.splice(payload.payload.oldIndex, 1);
                uiHandler.snippets[payload.working].items.splice(payload.payload.newIndex, 0, item);
            }
        };

        var container = null;
        $(document).ready(function() {
            var content = window.location.href.split("?");
            //
            // initialize firebase auth provider
            //
            storageApi.init();
            
            extensionRemoteApi.init(handlers);

            if (content.length > 1) {
                content = content[1];
                if (content.indexOf("save") >= 0) {
                    global_action = "SAVE";
                    //showContent(container);
                } // save
                else if (content.indexOf("open") >= 0) {
                    var content = content.substr(5);
                    var uid = content.substr(0, content.length - 1);
                    storageApi.loadContent("-" + uid, null, function(data) {
                        console.dir(data);
                        if (data) data.uid = uid;
                        showContent(data);
                    });
                }
                else if (content.indexOf("search") >= 0) {
                    var content = content.substr(7, content.length - 8);
                    storageApi.searchContent(content, function(content) {
                      showSearchResult(content);
                    });
                }

            } else { // content is not a query
                storageApi.searchContent(null, function(content) {
                    showSearchResult(content);
                });
            }

            function addSaveForm(payload) {
                // remove the previous form and create a new one
                $("#snipettor-save-form").remove();
                $('<form id="snipettor-save-form" action="#" uid="' + (payload.uid || '')+ '" class="navbar-form pull-left">\
    <button type="button" class="btn"><i class="fa fa-play">&nbsp;Play</i></button>&nbsp;&nbsp;&nbsp;\
    '+ (payload.uid ? '': '<button type="submit" class="btn"><i class="fa fa-delete">&nbsp;Delete</i></button>&nbsp;&nbsp;&nbsp;') + '\
    <button type="submit" class="btn"><i class="fa fa-save">&nbsp;Save</i></button>&nbsp;&nbsp;&nbsp;\
    '+ (payload.uid ? '<button type="submit" class="btn"><i class="fa fa-fork">&nbsp;Fork</i></button>': '')+ ' <br><br>\
  <div class="raw">\
        <label for="title">Title*    </label><br>\
        <input id="snippetor-save-title" class="form-control input-sm" type="text" class="span2" style="width: 550px;" value="' +
                    payload.title +
                    '"><br>\
    </div><br>\
    <div class="raw">\
        <label for="tags">Tags*</label><br>\
        <input id="snippetor-save-tags" type="text" value="'+( payload.tags || "") +'" class="form-control input-sm" data-role="tagsinput" class="span2" style="min-width: 550px;">\
    </div>\
    <div class="raw">\
        <label for="description">Description*</label><br>\
        <textarea id="snippetor-save-description" class="form-control input-sm" id="snippetor-save-description" rows="3" style="min-width: 550px;">'+(payload.description || "") +'</textarea>\
    </div>\
    <br>\
  </form>'
                ).appendTo("#main-content");
            }

            function showSearchResult(searchData) {
                for (var x in searchData) {
                    var description = searchData[x];
                    var xxx = x.substr(1);
                    $('<div class="container">\
<h2 ><div class="input-group" style="display:flex;"><a class="snippetor-play-snippet" uid="'+x+'"><p><i class="fa fa-play">Play</i></p></a> &nbsp;&nbsp;/&nbsp;&nbsp; <a class="snippetor-detail-snippet" href="/?open='+xxx+'"><p><i class="fa fa-edit">&nbsp;Details</i></p></a></div></h2>\
<div class="panel panel-default">\
<div class="panel-heading">' + description.title + '</div>\
<div class="panel-body">' + description.description +
                        '</div>\
</div>\
</div>').prependTo("#main-content");
                }
                $(".snippetor-play-snippet").click(function(){
                  var uid = $(this).attr("uid");
                  var payload = searchData[uid];
                  if (!uid)
                    return;

                  uid = uid.substr(1);
                  payload.uid = uid; // save current unique ID

                  extensionRemoteApi.openSnippet(payload);
                });
            }


            function showContent(payload) {
                
                addSaveForm(payload);
                for (var r = 0; r < payload.items.length; ++r) {
                    showContentItem(payload.items[r], null, extensionRemoteApi);

                } // for

                //
                // Save content to the firebase storage
                //
                $("#snipettor-save-form").submit(function(e) {

                  e.preventDefault();
                  e.stopPropagation();
                    
                  // Update title tags and description
                  payload.title = $("#snippetor-save-title").val();
                  payload.tags = $("#snippetor-save-tags").val();
                  payload.description = $("#snippetor-save-description").val();
                  
                  var uid = $(this).attr("uid");
                  if (!uid || uid == "") {
                    storageApi.saveContent(payload, function(path) {
                        var uniquePath = path.toString().split("/")[2];
                        uniquePath = uniquePath.substr(1);
                        // handle snippet save
                        extensionRemoteApi.onSavedSnippet({uid: uniquePath});
                        
                        setTimeout(function() {
                          window.location.href = "/?open=" + uniquePath;
                        }, 100);
                    });
                  } else {
                    storageApi.updateContent(uid, payload, function(path) {
                        var uniquePath = path.toString().split("/")[2];
                        uniquePath = uniquePath.substr(1);

                        // handle snippet saved
                        extensionRemoteApi.onSavedSnippet({uid: uniquePath});

                        setTimeout(function() {
                          window.location.href = "/?open=" + uniquePath;
                        }, 100);
                     }); // update content API
                }
                return false;
              }); // on sumbit method
            } // show Content method

         uiHandler = {
             snippets: [],
             working: -1,
             updateWorkingSnippets: function(snippets) {
                 $("ul#snippetor-select-menu>li.snippet-draft").remove();
                 for (var i in snippets) {
                     var sn = snippets[i];
                     if (sn)
                       $("ul#snippetor-select-menu").prepend('<li class="snippet-draft"><a href="#">'+sn.title+'</a></li>');
                 }
                 uiHandler.snippets = snippets;
             },
             handleWorkingSnippet: function(id) {
                 if (uiHandler.snippets.length > id) {
                     uiHandler.working = id;
                     if (global_action == "SAVE")
                       showContent(uiHandler.snippets[id]);
                 }
             },
             onSaveCurrentSnippet: function() {
                 window.location.href = "/";
             },
             doSwapItems: function(action) {
                 if (action.oldIndex == action.newIndex)
                   return;

                 var lis = $('div.panel-default');
                 console.dir(action);
                 console.dir(lis);
                 var ttt = lis.eq(action.oldIndex);
                 var new_ttt = lis.eq(action.newIndex);
                 console.dir(ttt);
                 if (action.newIndex < action.oldIndex) {
                   new_ttt.before(ttt);
                 } else {
                   new_ttt.after(ttt);
                 }
             }
         };


        }); // document ready
    </script>

</body>

</html>

{'fraction_non_alphanumeric': 0.1375208107714937, 'fraction_numerical': 0.005107731100586743, 'mean_word_length': 1.3516800973088035, 'pattern_counts': {'":': 0, '<': 557, '<?xml version=': 0, '>': 569, 'https://': 22, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3452500', 'n_tokens_mistral': 16272, 'n_tokens_neox': 15108, 'n_words': 2785}