User:Md gilbert/vte.js

// - this prevents double left braces being misinterpreted by the MediaWiki parser

// global variables, as required var vte_sock = true; var action = "" + " " + " Sorry, your browser does not support inline SVG." + " "; var data_api = "https://alahele.ischool.uw.edu:8997";

var vte = { // initialize - application constructor initialize: function { // Load the external libraries var head = document.getElementsByTagName("head")[0]; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = data_api + '/static/d3.min.js'; head.appendChild(script); script = document.createElement('script'); script.type = 'text/javascript'; script.src = data_api + '/static/socket.io-1.2.0.js'; head.appendChild(script); script = document.createElement('script'); script.type = 'text/javascript'; script.src = data_api + '/static/wiky.js'; head.appendChild(script); //wiky.js didn't work for what we needed, trying Pilaf's InstaView //Grabbed from https://en.wikipedia.org/wiki/User:Pilaf/instaview.js   script = document.createElement('script'); script.type = 'text/javascript'; script.src = data_api + '/static/instaview.js'; head.appendChild(script);

// fdeb requires d3, make sure it's loaded first var t1 = setInterval(function {     if (typeof(d3) != 'undefined') {        clearInterval(t1);        script = document.createElement('script');        script.type = 'text/javascript';        script.src = data_api + '/static/fdeb.js';        head.appendChild(script);      }    }, 100);

// Create the VTE button var $btn = $(     " " +      "  VTE " +      " "    ).attr("title", "Open the Virtual Team Explorer"); // Add the button to the left of the search box $("#p-search").before($btn); // Define our click action $("#p-vte").on("click", function {     console.log("opening vte");      vte.setCookie("vte-view", "Explorer");      vte.setCookie("vte-status", "Open");      $("#vte-window").show;    });

// Preload the vte once required variables are loaded (ie, socket.io) var t2 = setInterval(function {     if (typeof(vte_sock.emit) !== 'undefined') {        clearInterval(t2);        vte.renderOverlay;      }    }, 100); }, // end initialize

// renderOverlay - draws the initial vte lightbox renderOverlay: function { // Emit vte load vte_sock.emit("vte_load", {     name: mw.config.get("wgUserName"),      time: new Date,      page: mw.config.get("wgTitle"),      namespace: mw.config.get("wgNamespaceNumber"),    });

// If the window already exists, just display it   if ( $("#vte-window").length > 0 ) { $("#vte-window").show; return; }

// Otherwise, create the vte window var $vteWindow = $(     " " +      "  " +      "    " +      "      Online users: " +      "    " +      "    " +      "  " +      "  " +      "    " +      "    " +      "    " +      "    " +      "      " +      "      " +      "      " +      "      " +      "      " +      "      " +      "    " +      "  " +      "  " +      " "    ); // Create and style the main vte window $vteWindow.css(s_vteWindow); $("#content").append($vteWindow); $("#vte-window-left").css(s_vteWindowLeft); $("#vte-window-left-online").css(s_vteWindowLeftOnline); $("#vte-window-left-chat").css(s_vteWindowLeftChat); $("#vte-window-right").css(s_vteWindowRight); $("#vte-window-right-title").css(s_vteWindowRightTitle); $("#vte-window-right-tool").css(s_vteWindowRightTool); $("#vte-window-right-nav").css(s_vteWindowRightNav); $("#vte-window-right-content").css(s_vteWindowRightContent); // Initially hide the window (we render on page load, only display if it was previously open) $("#vte-window").css("display", "none"); // Initially hide each of the content pages (ie, loading, explorer, summary, etc) $(".vte-page").css("display", "none"); // Populate the loading page $("#vte-window-loading").html(     " " +      "Loading..." +      " "    ); $("#vte-window-loading").css(s_vteWindowLoading);

// Fill in basic vte elements vte.populateTitle; vte.populateChat; // Get project and active project information vte.getProjectData;

// If we render the overlay from a WikiProject page, preload the summary for that project // The general flow is: // 1) Draw the initial VTE window and request project data   // 2) Once project data is received, we have a few options: //  2.0) If the VTE was previously open, draw it immediately with a loading window. The steps    //        below will populate the proper content.    //   2.1) Whether or not we're on a project page, if the VTE was not previously open, get the //       project data, draw the project explorer, but don't show the VTE. //  2.2) If we're on a project page and the VTE was not previously closed, draw the VTE at the     //        project summary view.    //   2.3) If we're not on a project page but the VTE was previously open to a project, draw the //       VTE with the previously opened project's summary. //  2.4) If we're not on a project page but the VTE was previously open to the project explorer,    //        open the VTE to the project explorer.

// 2.0) If the VTE was previously open, draw it immediately and show the loading page.   var stat = vte.getCookie("vte-status");    var view = vte.getCookie("vte-view");    if (stat != null && stat != "Closed") {      $("#vte-window").show;      $("#vte-window-loading").show;    }    var t1 = setInterval(function { if (typeof($("#vte-window").data("vte-projects")) !== "undefined") { // Projects loaded, clear the interval and compare with the current page clearInterval(t1); // 2.1) Populate the project explorer page, regardless of page or whether the VTE was open       vte.populateProjectSelect;        var t2 = setInterval(function { if (typeof($("#vte-window").data("vte-active-projects")) !== "undefined") { clearInterval(t2); vte.populateProjectExplorer; }       }, 100);

// 2.2) Iterate over projects and see if we're on a project page       var index = -1;        for (var i = 0; i < $("#vte-window").data("vte-projects").result.length; i++) {          if (mw.config.get('wgTitle').replace(/ /g, "_") == $("#vte-window").data("vte-projects").result[i].p_title) {            index = i;            break;          }        }        if (mw.config.get('wgNamespaceNumber') == 4 && index != -1) {          // We've got a match.  Set data and draw the summary          console.log("vte - Loading project summary");          $("#vte-window").data("vte-project", { title: $("#vte-window").data("vte-projects").result[index].p_title, id: $("#vte-window").data("vte-projects").result[index].p_id, created: $("#vte-window").data("vte-projects").result[index].p_created, members: {}, tasks: {}, });         vte.pageTransition("vte-window-summary", function { vte.populateNav; vte.populateProjectSummary; });       } else {          // 2.3) If we're not on a project page, still render that project page if the project cookie is set var project = vte.getCookie("vte-project"); if (project) { console.log("vte - not on a project page but vte-project cookie set: " + project.title); $("#vte-window").data("vte-project", project); vte.pageTransition("vte-window-summary", function {             vte.populateNav;              vte.populateProjectSummary;            }); } else { // 2.4) Otherwise, switch to the project explorer page           vte.pageTransition("vte-window-explorer", function { $(".vte-page").hide; $("#vte-window-explorer").show; });         }        }      } else {        // Projects aren't loaded yet, keep waiting...      }          }, 100); },

// getProjectData - Called when the vte is rendered on page load. Requests active and all projects. getProjectData: function { // First try to load the project data from Storage variables // (see http://www.w3schools.com/html/html5_webstorage.asp) var vte_projects = vte.getStorage("vte-projects"); var vte_active_projects = vte.getStorage("vte-active-projects"); if (vte_projects !== null && vte_active_projects !== null) { console.log("vte - found project data in localStorage"); $("#vte-window").data("vte-projects", vte_projects); $("#vte-window").data("vte-active-projects", vte_active_projects); return true; }

console.log("vte - fetching project data from API");

// Request all projects var url = data_api + '/api/getProjects'; $.ajax({     url: url,      dataType: "json",      success: function(data, stat, xhr) {        if (data.errorstatus != "success") {          console.error("Failed to request projects: " + data.message);          return false;        }        $("#vte-window").data("vte-projects", data);        vte.setStorage("vte-projects", data, {expires: 7});      },      error: function(xhr, stat, err) {        console.error("Failed to request project data from API: " + JSON.stringify(xhr));      },    }); // Request active projects var url = data_api + "/api/getActiveProjects?group=project|namespace&compress=project"; $.ajax({     url: url,      dataType: "json",      success: function(data, stat, xhr) {        if (data.errorstatus != "success") {          console.error("Failed to request active projects: " + data.message);          return false;        }        // Add in the ratio        for (var i in data.result) {          data.result[i].ratio = data.result[i].total_edits / data.result[i].total_pages;        }        $("#vte-window").data("vte-active-projects", data);        vte.setStorage("vte-active-projects", data, {expires: 7});      },      error: function(xhr, stat, err) {        console.error("Failed to request active projects: " + JSON.stringify(xhr));      }    }); },

// getWikiPage - requests page content for the last revision of a wiki page // obj can contain: //  title: The page title to request data for //  onCreate: Function called if the requested page doesn't exist //  onSuccess: Function called on successfully fetching the page //  onFailure: Function called on failing to fetch the page getWikiPage: function(obj) { if (typeof(obj) !== 'object') obj = {}; var title = obj.title; if (! title) { console.error("getWikiPage: 'title' argument is required"); return false; }   if (! ("onSuccess" in obj)) obj.onSuccess = function {}; if (! ("onFailure" in obj)) obj.onFailure = function {}; if (! ("onCreate" in obj)) obj.onCreate = function {}; $.getJSON(     mw.util.wikiScript('api'),      {        format: "json",        action: "query",        prop: "revisions",        rvprop: "content",        rvlimit: 1,        titles: title,      }    ) .done(function(data) {     var page, text;      //try {        for (page in data.query.pages) {          text = data.query.pages[page].revisions[0]["*"];        }        obj.onSuccess(text); /*      } catch(e) {        // If the page is missing call obj.onCreate        if ("-1" in data.query.pages && data.query.pages["-1"].missing == "") {          console.log("Requested page not found: " + obj.title);          obj.onCreate;        } else {          obj.onFailure(e);        }      }    }) .fail(function(e) {     obj.onFailure(e);    }); }, // updateWikiPage - updates a wiki page with a given string // obj can contain: //  title: The page title to update //  text: The full text of the updated page //  summary: The summary for the revision //  onSuccess: Function called on successful updates //  onFailure: Function called on failing to update updateWikiPage: function(obj) { if (typeof(obj) !== 'object') obj = {}; var title = obj.title; if (! title) { console.error("getWikiPage: 'title' argument is required"); return false; }   if (! ("onSuccess" in obj)) obj.onSuccess = function {}; if (! ("onFailure" in obj)) obj.onFailure = function {}; if (! ("summary" in obj)) obj.summary = "[VTE] Updating page contents"; // Make the request to update the page $.ajax({     url: mw.util.wikiScript( 'api' ),      type: 'POST',      dataType: 'json',      data: {        format: 'json',        action: 'edit',        title: obj.title,        text: obj.text, // will replace entire page content        summary: obj.summary,        token: mw.user.tokens.get( 'editToken' )      }    }) .done( obj.onSuccess ) .fail( obj.onFailure ); }, // getTaskData - requests data from the Tasks page for this project, creates the page if it doesn't exist getTaskData: function { var vte_project = $("#vte-window").data("vte-project"); var obj = { title: "User:Vtebot/" + vte_project.title + "/Tasks", onCreate: function { vte.updateTaskData; vte.getTaskData; },     onSuccess: function(text) { var res = vte.parseTable(text, "tasks"); vte_project = $("#vte-window").data("vte-project"); vte_project.tasks = res; $("#vte-window").data("vte-project", vte_project); },     onFailure: function(e) { console.error("Failed to request wiki page: " + JSON.stringify(e)); },   };    vte.getWikiPage(obj); }, // getTaskTalkData - requests data from the Tasks Talk page for this project, create if it doesn't exist getTaskTalkData: function { var vte_project = $("#vte-window").data("vte-project"); var obj = { title: "User_talk:Vtebot/" + vte_project.title + "/Tasks", onCreate: function { vte.updateTaskTalkData; vte.getTaskTalkData; },     onSuccess: function(text) { var res = vte.parseTalk(text, "tasks_talk"); vte_project = $("#vte-window").data("vte-project"); vte_project.tasks_talk = res; $("#vte-window").data("vte-project", vte_project); },     onFailure: function(e) { console.error("Failed to request wiki talk page: " + JSON.stringify(e)); },   };    vte.getWikiPage(obj); }, // getMemberData - requests data from the Members page for this project, creates the page if it doesn't exist. //  This function will /also/ grab procedural members, ie, those required to build out the social network //  for the project and inform the import function. getMemberData: function { var vte_project = $("#vte-window").data("vte-project"); var obj = { title: "User:Vtebot/" + vte_project.title + "/Members", onCreate: function { vte.updateMemberData; vte.getMemberData; },     onSuccess: function(text) { var res = vte.parseTable(text, "members"); vte_project = $("#vte-window").data("vte-project"); vte_project.members = res; $("#vte-window").data("vte-project", vte_project); },     onFailure: function(e) { console.error("Failed to request Members page: " + JSON.stringify(e)); },   };    vte.getWikiPage(obj);

/****    * Grab the procedural member data. This will require 4 data requests: * 1) Get the top  editors to the current project, subpages, and talk pages.    * 2) Get all users who have links on the project page and all sub-pages (not talk pages). * Note - Users flagged 3 ways: user link on page, edit to page, edit to talk page * 3) Get all pages that are under the scope of this project.    * 4) For each of those user, get the top  pages each of those *   editors edited, * Note - Pages flagged 2 ways: in-project or out-project *    * And that's it. * Queries 1, 2, and 3 can be run concurrently. 4 depends on 1 and 2. ****/

// object to hold the data var res = { editors: {}, links: {}, p_pages: {}, pages: {}, };   // Boolean to track ongoing requests var complete = { editors: 0, links: 0, p_pages: 0, pages: 0, };

// 1) Fetch top editors to the project page, sub-pages, and corresponding talk pages   $.ajax({ url: data_api + "/api/getEdits?", data: { page: vte_project.title, //sd:, // Default range is 1 year, ending now, which should be appropriate //ed: , group: "user|page|date", subpages: 1, namespace: "4|5", //limit: topEditors, excludeBots: 1, },     dataType: "json", error: function(xhr, stat, err) { console.error("Failed to request data, project editors. Response: " + JSON.stringify(xhr)); },     success: function(data, stat, xhr) { // Check for error if (data.errorstatus == 'fail') { console.error("Error: Failed to request data, project editors: " + data.message); }       // Save the results complete.editors = 1; res.editors = data.result; if (res.editors.length == 0) { console.warn("No edits to the project page or subpages found for project: " + vte_project.title); }     }    });

// 2) Get all users who have links on the project page and all sub-pages   $.ajax({ url: data_api + "/api/getProjectMembers?", data: { project: vte_project.title, //sd:, // Default time span is 1 year, which is appropriate for now. //ed: , },     dataType: "json", error: function(xhr, stat, err) { console.error("Failed to request data, member links: " + JSON.stringify(xhr)); },     success: function(data, stat, xhr) { // Check for error if (data.errorstatus == 'fail') { console.error("Error: Failed to request data, project members: " + data.message); }       // Save the results complete.links = 1; res.links = data.result; if (Object.keys(res.links).length == 0) { console.warn("No project member user links found for project: " + vte_project.title); }     }    });

// 3) Get all pages that are under the scope of this project   $.ajax({ url: data_api + "/api/getProjectPages?", data: { project: vte_project.title, },     dataType: "json", error: function(xhr, stat, err) { console.error("Failed to request data, project pages: " + JSON.stringify(xhr)); },     success: function(data, stat, xhr) { // Check for error if (data.errorstatus == 'fail') { console.error("Error: Failed to request data, project pages: " + data.message); }       // Save the results complete.p_pages = 1; res.p_pages = data.result; if (Object.keys(res.p_pages).length == 0) { $rlog("No pages found for project: " + vte_project.title); }     }    });

// 4) For each of the users, get the top  pages each of those users edited.   // This depends on 1 & 2 above, so wait until they're done to complete    var start = new Date.getTime;    var t = setInterval( function { if (complete.editors == 1 && complete.links == 1) { clearInterval(t); // Grab the users from editors and links and look for all pages they edited var uids = []; for (var u in res.links) { for (var p in res.links[u]) { if (res.links[u][p].link_count > 0 && res.links[u][p].pm_user_id != 0) uids.push(res.links[u][p].pm_user_id); }       }        for (var i in res.editors) { if (res.editors[i].tu_id != 0) uids.push(res.editors[i].tu_id); }       // And then, finally, we collect the edit histories of the users who worked on        // the project page (editors) and those who placed their user links on the project // page (links). $.ajax({         url: data_api + "/api/getEdits?",          data: {            userid: uids.join("|"),            //sd:, // Default is to get edits for 1 year, ending now, which is fine.            //ed: ,            group: "user|page",            namespace: "0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|100|101|108|109|118|119|446|447|710|711|828|829",            limit: 2000, // Limiting mostly to reduce download size of result, usually about 3-4 Mb            excludeBots: 1,          },          dataType: "json",          error: function(xhr, stat, err) {            console.error("Failed to request data, global edits: " + JSON.stringify(xhr));          },          success: function(data, stat, xhr) {            // Check for an error            if (data.errorstatus == 'fail') {              console.error("Error, failed to request data, global edits: " + data.message);            }            // Save the results complete.pages = 1; res.pages = data.result; if (res.pages.length == 0) { console.warn("No edits found for editors and members of project: " + vte_project.title); }         }        });

} else { // Timeout after 60 seconds if ( (new Date.getTime) - start >= 60000) { clearInterval(t); console.error("Timed out requesting project network data: " + JSON.stringify(complete)); }     }    }, 100);

// Wait until all 4 data requests are complete var t1 = setInterval( function {     if (complete.editors == 1 && complete.links == 1 && complete.p_pages == 1 && complete.pages == 1) {         // We're all done, clear the interval and save the data          clearInterval(t1);          vte_project = $("#vte-window").data("vte-project");          vte_project.members_network = res;          $("#vte-window").data("vte-project", vte_project);      } else {        // Timeout after 60 seconds        if ( (new Date.getTime) - start >= 60000) {          clearInterval(t1);          console.error("Timed out requesting project network data: " + JSON.stringify(complete));        }      }    }, 100);

}, // getMemberTalkData - requests data from the Talk page for this project's members, creates it if needed getMemberTalkData: function { var vte_project = $("#vte-window").data("vte-project"); var obj = { title: "User_talk:Vtebot/" + vte_project.title + "/Members", onCreate: function { vte.updateMemberTalkData; vte.getMemberTalkData; },     onSuccess: function(text) { var res = vte.parseTalk(text, "members_talk"); vte_project = $("#vte-window").data("vte-project"); vte_project.members_talk = res; $("#vte-window").data("vte-project", vte_project); },     onFailure: function(e) { console.error("Failed to request wiki talk page: " + JSON.stringify(e)); },   };  },

// updateTaskData - will update the current task list with data from $("#vte-window").data("vte-project").tasks //  and create a corresponding talk page section, saved in $("#vte-window").data("vte-project").tasks_talk updateTaskData: function(onSuccess, onFailure) { if (typeof(onSuccess) === 'undefined') onSuccess = function{}; if (typeof(onFailure) === 'undefined') onFailure = function{}; var vte_project = $("#vte-window").data("vte-project"); var title      = "User:Vtebot/" + vte_project.title + "/Tasks";

// Build the page text, if we don't currently have any tasks we're probably creating the stub page var tasks_str = ""; if ($.isEmptyObject(vte_project.tasks)) { // Create the page with the tasks stub, default display is task title, description, and priority tasks_str = "\n\n" + "";   } else { // Otherwise, build the task string from the tasks object tasks_str = vte_project.tasks.pre + "" + vte_project.tasks.post; }

var obj = { title: title, text: tasks_str, summary: "[VTE] Updating details for task: " + $("#vte-task-title").val, onSuccess: function { // Emit vte update vte_sock.emit("update", {         name: mw.config.get("wgUserName"),          time: new Date,          page: mw.config.get("wgTitle"),          namespace: mw.config.get("wgNamespaceNumber"),          project: $("#vte-window").data("vte-project").title,          view: "Tasks",        }); onSuccess; },     onFailure: function(e) { console.error("Failed to update tasks page: " + JSON.stringify(e)); onFailure; },   };    //vte.updateWikiPage(obj); onSuccess; }, // updateTaskTalkData - will update the current task talk data from $("#vte-window").data("vte-project").task_talk updateTaskTalkData: function(onSuccess, onFailure) { if (typeof(onSuccess) === 'undefined') onSuccess = function{}; if (typeof(onFailure) === 'undefined') onFailure = function{}; var vte_project = $("#vte-window").data("vte-project"); var title = "User_talk:Vtebot/" + vte_project.title + "/Tasks";

// Build the Tasks Talk page string, if we don't currently have any content we're probably creating the page var talk_str = ""; if ($.isEmptyObject(vte_project.tasks_talk)) { // Create the page with the talk stub (empty string) talk_str = ""; } else { // Otherwise, build the talk page string from the tasks_talk object for (var task in vte_project.tasks_talk) { talk_str += "== " + task + " ==\n"; for (var i in vte_project.tasks_talk[task]) { var obj = vte_project.tasks_talk[task][i]; talk_str += Array(obj.level + 1).join(":") + obj.msg + "\n"; }     }    }

// Finally, request the page update var obj = { title: title, text: talk_str, summary: "[VTE] Updating Tasks Talk page", onSuccess: function { onSuccess; },     onFailure: function(e) { console.error("Failed to update Tasks Talk page: " + JSON.stringify(e)); onFailure(e); },   };    //vte.updateWikiPage(obj); onSuccess; }, // updateMemberData - will update the current member list with data from // $("#vte-window").data("vte-project").members updateMemberData: function(onSuccess, onFailure) { if (typeof(onSuccess) === 'undefined') onSuccess = function{}; if (typeof(onFailure) === 'undefined') onFailure = function{}; var vte_project = $("#vte-window").data("vte-project"); var title      = "User:Vtebot/" + vte_project.title + "/Members";

// Build the page text, if we don't currently have any members we're probably creating the stub page var members_str = ""; if ($.isEmptyObject(vte_project.members)) { // Create the page with the members stub, default display is member name and interests members_str = "\n\n" + "";   } else { // Otherwise, build the member string from the members object members_str = vte_project.members.pre + "{{#invoke:ListMaster|printTable|style=" + vte_project.members.style + "|display=" + vte_project.members.display + "|\n"; for (var i in vte_project.members.struc) { members_str += " {{#"; var attribs = []; for (var n in vte_project.members.struc[i]) { attribs.push(n + "=" + vte_project.members.struc[i][n]); }       members_str += attribs.join("|") + "}}\n"; }     members_str += vte_project.members.post; }

var obj = { title: title, text: members_str, summary: "[VTE] Updating project members", onSuccess: function { // Emit vte update vte_sock.emit("update", {         name: mw.config.get("wgUserName"),          time: new Date,          page: mw.config.get("wgTitle"),          namespace: mw.config.get("wgNamespaceNumber"),          project: $("#vte-window").data("vte-project").title,          view: "Members",        }); onSuccess; },     onFailure: function(e) { console.error("Failed to update project members: " + JSON.stringify(e)); onFailure; },   };    vte.updateWikiPage(obj); }, // updateMemberTalkData - will update the current member talk data from // $("#vte-window").data("vte-project").members_talk updateMemberTalkData: function(onSuccess, onFailure) { if (typeof(onSuccess) === 'undefined') onSuccess = function{}; if (typeof(onFailure) === 'undefined') onFailure = function{}; var vte_project = $("#vte-window").data("vte-project"); var title = "User_talk:Vtebot/" + vte_project.title + "/Members";

// Build the Memberss Talk page string, if we don't currently have any content we're probably creating the page var talk_str = ""; if ($.isEmptyObject(vte_project.members_talk)) { // Create the page with the talk stub (empty string) talk_str = ""; } else { // Otherwise, build the talk page string from the members_talk object for (var member in vte_project.members_talk) { talk_str += "== " + member + " ==\n"; for (var i in vte_project.members_talk[member]) { var obj = vte_project.members_talk[member][i]; talk_str += Array(obj.level + 1).join(":") + obj.msg + "\n"; }     }    }

// Finally, request the page update var obj = { title: title, text: talk_str, summary: "[VTE] Updating Members Talk page", onSuccess: function { onSuccess; },     onFailure: function(e) { console.error("Failed to update Members Talk page: " + JSON.stringify(e)); onFailure(e); },   };    //vte.updateWikiPage(obj); onSuccess; },

// populateTitle - draws title bar content populateTitle: function { var $vteTitle = $(     " Virtual Team Explorer " +      " " +      "  " +      "    " +      "  " +      "  " +      "    " +      "  " +      "  " +      "    " +      "  " +      " "    ); // Attributions, via Wikimedia Commons: // User: By GNOME icon artists (GNOME download / GNOME FTP) [GPL (http://www.gnu.org/licenses/gpl.html)] // Gear: By MGalloway (WMF) (Own work) [CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0)] // Close: By MGalloway (WMF) (Own work) [CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0)]

// Add the title and style the elements $("#vte-window-right-title").html($vteTitle); $("#vte-title").css(s_vteTitle); $("#vte-title-actions").css(s_vteTitleActions); $("#vte-title-action-user").css(s_vteTitleAction); $("#vte-title-action-settings").css(s_vteTitleAction); $("#vte-title-action-close").css(s_vteTitleAction); // Add the actions $("#vte-title-action-user").on("click", function {

});   $("#vte-title-action-settings").on("click", function {

});   $("#vte-title-action-close").on("click", function { console.log("closing the vte (will maintain current view)."); vte.setCookie("vte-status", "Closed"); $("#vte-window").hide; }); },

// populateProjectSelect - draws the project browser content populateProjectSelect: function { // Add search box and main nav var $projectSelect = $(     "" +      " "    ); // Add it   $("#vte-window-right-tool").html($projectSelect); $("#vte-project-select-multi").hide; // Style it   $("#vte-project-select-label").css(s_vteProjectSelectLabel); $("#vte-project-select-input").css(s_vteProjectSelectInput);

// For each of the projects, add it to the dropdown var projects = $("#vte-window").data("vte-projects").result; $.each(projects, function(i,v) {     var project = projects[i]['p_title'].replace(/_/g, " ").toLowerCase;      var input = $("#vte-project-select-input").val.replace(/_/, " ").toLowerCase;      $("#vte-project-select-multi").append( "" + projects[i]['p_title'].replace(/_/g, " ") + " "     );    });    // Then style the things $("#vte-project-select-multi").css(s_vteProjectSelectMulti); $(".vte-project-select-multi-proj").css(s_vteProjectSelectMultiProj); // Add hover color for project $(".vte-project-select-multi-proj").hover(     function {        $( this ).css("color", "#3B0B0B");      }, function {        $( this ).css("color", "#000");      }    ); // Add the action to watch for keyup in the project input vte.updateProjectSelect; // Add click action to hide the list $("body").on("click", function(evt) {     $("#vte-project-select-multi").hide;    }); // Add click action to load project summary $(".vte-project-select-multi-proj").on("click", function(evt) {     var id      = $(evt.currentTarget).attr("vte-p-id");      var title   = $(evt.currentTarget).attr("vte-p-title");      var seen    = $(evt.currentTarget).attr("vte-p-seen");      var touched = $(evt.currentTarget).attr("vte-p-touched");      var created = $(evt.currentTarget).attr("vte-p-created");      // Clear the project selection div      $("#vte-project-select-multi").remove;      // Load the project summary      console.log("loading summary for project " + title + ", id: " + id);      $("#vte-window").data("vte-project", { title: title, id: id, created: created, members: {}, tasks: {}, });     vte.pageTransition("vte-window-summary", function { vte.populateNav; vte.populateProjectSummary; });   });  },

updateProjectSelect: function { // Add the actions (everytime there's a key-up, update list of visible projects) $("#vte-project-select-input").on("keyup", function {     var input = $("#vte-project-select-input").val.replace(/ /g,"_").toLowerCase;      // FIRST, update the list of active projects      $(".vte-active-project").each(function(i, v) { var project = $(v).attr("p_title").replace(/ /g, "_").toLowerCase; if (project.indexOf(input) != -1) { $(v).css("display", "block"); } else { $(v).css("display", "none"); }     });      // SECOND, update the list from the multi-select dropdown      // Make sure we're showing the selection div      $("#vte-project-select-multi").show;      $(".vte-project-select-multi-proj").each(function(i,v) { var project = $(v).attr("vte-p-title").replace(/ /g, "_").toLowerCase; if (project.indexOf(input) != -1) { $(v).css("display", "block"); } else { $(v).css("display", "none"); }     });      // Print a message if no projects match the input      if ($(".vte-project-select-multi-proj").not(":hidden").length == 0) {        $("#vte-project-select-multi").append( " " +         "  No matching projects found" + " "       );      } else {        $(".vte-project-select-multi-none").remove;      }    }); },

populateProjectExplorer: function { // Clear the content div, print initial greeting $("#vte-window-explorer").html(     " " +      "  Search for a WikiProject in the box above, or select from the list of most " +      "  active WikiProjects below to continue. " +      "  (Projects below represent the most active WikiProjects by edits to " + " member pages within the last month, limited to those with at least 30 edits)" +      " " +      " "    ); $("#vte-summary-instructions").css(s_vteSummaryInstructions);

// Add in buttons to sort projects by edits, pages edited, or edits per page $("#vte-summary-projects").append(     " " +      "  " +      " " +      " " +      "  " +      " " +      " " +      "  " +      " " +      " " +      "  " +      " "    );

// Add the project thumbnail for each of the most active projects var active = $("#vte-window").data("vte-active-projects").result; $.each(active, function(i,v) {     if (v.total_edits < 30) return true;      var proj = v;      // Mark the style as hidden if the project doesn't match the search input box      var project = proj.p_title.replace(/ /g, "_").toLowerCase;      var input = $("#vte-project-select-input").val.replace(/ /g,"_").toLowerCase;      var style = project.indexOf(input) != -1 ? " style='display: block;' " : " style='display: none;' ";      $("#vte-summary-projects").append( "" + "  " +        " "      );      // Save data on the div for sorting      $("#vte-active-project-" + proj.p_id).data("sort", { edits: proj.total_edits, pages: proj.total_pages, ratio: proj.ratio, project_edits: proj["4"], });

// Add the hover action $("#vte-active-project-" + proj.p_id).hover(       function {          $(this).css("border", "solid 1px #848484");        }, function {          $(this).css("border", "solid 1px #000000");        }      );

// Add the click action to the thumbnail $("#vte-active-project-" + proj.p_id).click(function(e) {       // Set project attributes        $("#vte-window").data("vte-project", { title: proj.p_title, id: $(e.currentTarget).attr("p_id"), title: $(e.currentTarget).attr("p_title"), created: $(e.currentTarget).attr("p_created"), });       // Draw the page        vte.pageTransition("vte-window-summary", function { vte.populateNav; vte.populateProjectSummary; });     });    });    // Style the thumbnails    $(".vte-active-project").css(s_vteActiveProject);    $(".vte-active-project-title").css(s_vteActiveProjectTitle);    $(".vte-active-project-label").css(s_vteActiveProjectLabel);    $(".vte-active-project-value").css(s_vteActiveProjectValue);    $(".vte-sort-summary-div").css(s_vteSortSummaryDiv);

// Add the action to sort $(".vte-sort-summary").button.click(function(e) {     var sort_by = $(e.currentTarget).attr("vte-sort-summary-by");      var s = $("#vte-window").data("vte-active-projects-sort");      var items = $(".vte-active-project").sort(function(a,b) { var da = $(a).data("sort")[sort_by]; var db = $(b).data("sort")[sort_by]; if (s.by == sort_by && s.direction == "desc") { $("#vte-window").data("vte-active-projects-sort", {by: sort_by, direction: "asc"}); return (da < db) ? -1 : (da > db) ? 1 : 0;       } else { $("#vte-window").data("vte-active-projects-sort", {by: sort_by, direction: "desc"}); return (db < da) ? -1 : (db > da) ? 1 : 0;       }      });      $("#vte-summary-projects").append(items);    });

// Trigger the initial sort action $("#vte-window").data("vte-active-projects-sort", {by: "edits", direction: "asc"}); $("#vte-sort-summary-by-edits").click; },

// populateProjectSummary - draws summary information for the project once it is selected //  from the vte-project-select-multi dropdown (or clicked on) populateProjectSummary: function { // Update the project search input $("#vte-project-select-input").val( $("#vte-window").data("vte-project").title.replace(/_/g," ") ); // Style the input $("#vte-project-select-input").prop("disabled", true); $("#vte-project-select-input").css("color", "#A4A4A4");

// Request/create the Tasks and Members pages under the vtebot user page. vte.getTaskData; vte.getTaskTalkData; //vte.getMemberData;

// Set the project cookies vte.setCookie("vte-project", $("#vte-window").data("vte-project")); vte.setCookie("vte-view", "Summary");

var title, id, created; title = $("#vte-window").data("vte-project").title; id = $("#vte-window").data("vte-project").id; created = $("#vte-window").data("vte-project").created; // Emit vte project select vte_sock.emit("project_load", {     name: mw.config.get("wgUserName"),      time: new Date,      page: mw.config.get("wgTitle"),      namespace: mw.config.get("wgNamespaceNumber"),      project: $("#vte-window").data("vte-project").title,    });

// Add the close icon $("#vte-project-select-input").after(""); $(".vte-close-project").css(s_vteCloseProject); $(".vte-close-project").button.click(function {     $("#vte-window").data("vte-project", false);      $(".vte-close-project").remove;      $("#vte-project-select-input").val("");      $("#vte-project-select-input").prop("disabled", false);      $("#vte-project-select-input").css("color", "#000000");      vte.removeCookie("vte-project");      vte.setCookie("vte-view", "Explorer");      vte.pageTransition("vte-window-explorer", function { vte.populateProjectExplorer; vte.populateNav; });   });

// Clear any existing data in the content window and add summary divs $("#vte-window-summary").html(     " " +      "  " +       "  " +      "    Edits to Project (blue) and Project Talk (grey) pages" +      "    " +      "    Loading project edit data... " +      "    " +      "  " +      "  " +      "    Most active articles in the last 30 days (showing the last year)" +      "    " +      "    Loading revision history for project pages... " +      "  " +      " "    ); $("#vte-window-right-content-summary").css(s_vteWindowRightContentSummary); $(".vte-loading").css(s_vteLoadingText); $("#vte-window-right-content-summary-p-edits").css(s_vteWindowRightContentSummaryGraph); $("#vte-window-right-content-summary-pages").css(s_vteWindowRightContentSummaryPages); $("#vte-window-right-content-summary-new").css(s_vteWindowRightContentSummaryNew); // Dynamically set the width so we don't get squished graphs if they're loaded too quickly. // The graphs should be in the right-content, which is 80% of vte-window, which is 80% of the // total width, minus padding (5px * 2 for vte-window right, 8px * 2 for the graph divs, 26px padding). var width = (window.innerWidth * .8 * .8) - 52; $("#vte-project-summary-graph").css("width", width + "px");

// Request summary data from our backend var t = title.replace(/ /g, "_"); var sd = created.substr(0, 8); var sw = vte.convertDateToWikiWeek(sd); var url = data_api + "/api/getEdits?page=" + t + "&namespace=4|5&group=page|user|date&sd=" + sd; $.ajax({     url: url,      dataType: "json",      success: function(data, stat, xhr) {        vte.drawProjectEdits(data, sw, "vte-project-summary-graph");      },      error: function(xhr, stat, err) {        console.error("Failed to request project edits: " + JSON.stringify(xhr));        $("#vte-window-right-content-summary").append("Failed to request project edits: " + JSON.stringify(xhr));      },      complete: function {        $("#vte-loading-edits").remove;      },    });

// Request most active project pages url = data_api + "/api/getActiveProjectPages?project_id=" + id; $.ajax({     url: url,      dataType: "json",      success: function(data, stat, xhr) {        // Once we've got recent active project pages, grab edit histories for those pages        var ids = [];        for (var i in data.result) {          if (data.result[i].tp_namespace == 0 || data.result[i].tp_namespace == 1)             ids.push(data.result[i].pa_page_id);        }        // We'll want to get edits for the last year        var now = new Date;        var sd = String(now.getFullYear-1) + String(vte.pad(now.getMonth+1,2)) +          String(vte.pad(now.getDate, 2));        var sw = vte.convertDateToWikiWeek(sd);        var ew = vte.convertDateToWikiWeek - 1;        url = data_api + "/api/getEdits?pageid=" + ids.join("|") + "&limit=0&namespace=0|1&group=page|user|date&sw=" + sw + "&ew=" + ew;        $.ajax({ url: url, dataType: "json", success: function(data,stat,xhr) { // Split the results by page var pages = {}; for (var i in data.result) { if (! pages.hasOwnProperty( data.result[i].rc_page_id )) pages[ data.result[i].rc_page_id ] = []; pages[ data.result[i].rc_page_id ].push( data.result[i] ); }           for (var id in pages) { //$.each(pages, function(i,v) {             // Create the graph div for each of the returned articles and draw the graph              $("#vte-window-right-content-summary-pages").append( " " + pages[id][0].tp_title.replace(/_/g," ") + " " + "<div class='vte-window-right-content-summary-page' " + " id='vte-window-right-content-summary-page-" + id + "' />" );             $("#vte-window-right-content-summary-page-" + id).css(s_vteWindowRightContentSummaryPage);              $("#vte-window-right-content-summary-page-" + id).css("width", width + "px");              vte.drawProjectEdits({result: pages[id]}, sw, "vte-window-right-content-summary-page-" + id);            }            $(".vte-summary-page-title").css(s_vteSummaryPageTitle);            // Add actions to article titles to set cookies and go to the page            $(".vte-summary-page-title").click(function(e) { var title = $(e.currentTarget).html.replace(/ /g, "_"); window.location.href = "/wiki/" + title; });         },          error: function(xhr, stat, err) {            console.error("Failed to request edits to most active articles: " + JSON.stringify(xhr));            $("#vte-window-right-content-summary").append("Failed to request active article edits: " + JSON.stringify(xhr));         },          complete: function {            $("#vte-loading-pages").remove;          },        }); },     error: function(xhr, stat, err) { console.error("Failed to request active project pages: " + JSON.stringify(xhr)); $("#vte-window-right-content-summary").append("Failed to request active project pages: " +          JSON.stringify(xhr)); },   });  },

// drawProjectEdits - draws summary edit information for a project and its corresponding Talk page drawProjectEdits: function(data, sw, div_id) { // Structure the edits var ew = vte.convertDateToWikiWeek; // This should be 1 greater than what was requested. var talk_edits = Array(ew - sw); var page_edits = Array(ew - sw); for (var i = 0; i < talk_edits.length; i++) talk_edits[i] = 0; for (var i = 0; i < page_edits.length; i++) page_edits[i] = 0; for (var i in data["result"]) { if (data["result"][i].rc_page_namespace % 2 == 0) page_edits[ data["result"][i].rc_wikiweek - sw ] += data["result"][i].rc_edits; if (data["result"][i].rc_page_namespace % 2 == 1) talk_edits[ data["result"][i].rc_wikiweek - sw ] += data["result"][i].rc_edits; }   // D3 sparkline graph // Get the width from the style of the parent element (the actual width from .width may not be    // correct if the element is still being drawn) var w = parseInt($("#" + div_id).css("width").replace("px", "")) - 10; var h = $("#" + div_id).height;

var t_max = d3.max(talk_edits); var p_max = d3.max(page_edits); var maxy = t_max > p_max ? t_max : p_max; var y = d3.scale.linear .domain([0, maxy]) .range([0, h]); var x = d3.scale.linear .domain([0, page_edits.length]) .range([0, w]); var vis = d3.select("#" + div_id) .append("svg:svg") .attr("width", w)     .attr("height", h); var g1 = vis.append("svg:g").attr("transform", "translate(2, " + h + ")"); var g2 = vis.append("svg:g").attr("transform", "translate(2, " + h + ")"); var line = d3.svg.line .x(function(d, i) {       return x(i);      }) .y(function(d) {       return -1 * y(d);      }); g1.append("svg:path").attr("d", line(page_edits)).style({"stroke": "#0000FF", "fill": "transparent"}); g2.append("svg:path").attr("d", line(talk_edits)).style({"stroke": "#545454", "fill": "transparent"});

// Add the legend text var count_text = [ { "cx": 10, "cy": 12, "text": maxy + " edits" }, { "cx": 10, "cy": h-5, "text": "0" } ];   var date_text = [ { "cx": w / 3, "text": vte.convertWikiWeekToDate( ((ew - sw) / 3) + sw ) }, { "cx": w * 2 / 3, "text": vte.convertWikiWeekToDate( ((ew - sw) * 2 / 3) + sw) } ];   var text_c = vis.selectAll("text.count") .data(count_text) .enter.append("text") .attr("x", function(d) { return d.cx; }) .attr("y", function(d) { return d.cy; }) .text( function(d) { return d.text; }) .attr("font-family", s_wpFont) .attr("font-size", "10px") .attr("fill", "#000000"); var text_d = vis.selectAll("text.date") .data(date_text) .enter.append("text") .attr("x", function(d) { return d.cx; }) .attr("y", function(d) { return 12; }) .text( function(d) { return d.text.substring(0,4) + "/"+d.text.substring(4,6) + "/"+d.text.substring(6,8); }) .attr("font-family", s_wpFont) .attr("font-size", "10px") .attr("text-anchor", "middle") .attr("fill", "#848484"); },

// populateNav - draws the navigation content populateNav: function { if ($("#vte-window").data("vte-project")) { // Make sure we're not duplicating the nav links (there was a bug where this happened that     // I can't seem to repro) $("#vte-window-right-nav").empty; $("#vte-window-right-nav").append(       " Communication " +        " Tasks " +        " Members " +        " Summary " +        " "      ); // Style it     $(".vte-content-nav").css(s_vteWindowRightContentTitle); $("#vte-summary").css("color", "#000");

// Add the click actions for the nav links $(".vte-content-nav").click(function(e) {       var id = $(e.currentTarget).attr("id");        if (id == "vte-summary") {          vte.pageTransition("vte-window-summary", function { $(".vte-content-nav").css("color", "#0B0B61"); $("#vte-summary").css("color", "#000"); vte.populateProjectSummary; });       } else if (id == "vte-members") {          vte.pageTransition("vte-window-members", function { $(".vte-content-nav").css("color", "#0B0B61"); $("#vte-members").css("color", "#000"); vte.clickMembers; });       } else if (id == "vte-tasks") {          vte.pageTransition("vte-window-tasks", function { $(".vte-content-nav").css("color", "#0B0B61"); $("#vte-tasks").css("color", "#000"); vte.clickTasks; });       } else if (id == "vte-communication") {          vte.pageTransition("vte-window-communication", function { $(".vte-content-nav").css("color", "#0B0B61"); $("#vte-communication").css("color", "#000"); vte.clickCommunication; });       } else {          console.error("Unknown vte action: " + id);        }      }); // And make sure it's visible $("#vte-window-right-nav").show; } else { $(".vte-content-nav, .vte-content-nav-spacer").remove; } },

// Function to handle page transitions pageTransition: function(page, load_function) { // Before the transition, hide all pages and show the loading window $(".vte-page").hide; $("#vte-window-right-nav").hide; $("#vte-window-loading").show; // Add pulse animation to loading text var i = 0; var t = setInterval(function {     if (i % 2 == 0) {        $("#vte-window-loading").animate({opacity: 0.3}, 1000, "linear");      } else {        $("#vte-window-loading").animate({opacity: 1.0}, 1000, "linear");      }      i++;    }, 1000); // Then load the page load_function; // Then switch to it   $(".vte-page").hide; vte.populateNav; $("#" + page).show; // And stop the pulse animation clearInterval(t); },

// Given a chunk of text, will return an object containing text before, after, and an array of  // top-level module invocations (won't parse modules in modules). Returns false if no modules found. parseInvocation: function(data) { //var obj = {pre: "", post: "", mods: []}; var obj = []; var s_index = 0, e_index = 0, s_paren = 0, c_paren = 0; for (var i = 0; i < data.length; i++) { if ((data.slice(i, i+3) == "{{#") && (s_paren + c_paren == 0)) s_index = i;     if (data[i] == "{") s_paren += 1; if (data[i] == "}") c_paren += 1; if ((s_paren == c_paren) && (s_paren + c_paren > 0)) { e_index = i + 1; s_paren = 0, c_paren = 0; obj.push({         pre: data.slice(0, s_index),          post: data.slice(e_index),          mod: data.slice(s_index, e_index),        }); }   }    if (s_paren > 0 || c_paren > 0) console.error("Uneven brace count, possible incorrect module declaration."); return obj.length > 0 ? obj : false; },

parseTable: function(data, table) { // Grab module invocation from the page text (at this level only accepting one table) var obj = vte.parseInvocation(data); //s_index = data.indexOf("{{#invoke:ListMaster"); if (! obj) { console.error("Failed to find module invocation, page contains: " + data); return false; }   // Then grab top-level submodule invocations from within this module var subs = [], mod = {}; for (var i in obj) { if (obj[i].mod.slice(0, 20) == "{{#invoke:ListMaster") { mod = obj[i]; subs = vte.parseInvocation(obj[i].mod.slice(3, -2)); }   }    if (Object.keys(mod).length == 0) { console.error("Module invocation on page, but not {{#invoke:ListMaster..."); return false; }

// Break apart sub-module invocations, grabbing columns for each row var struc = []; for (var i in subs) { // Strip the braces subs[i].mod = subs[i].mod.slice(3, -2); var attribs = subs[i].mod.split("|"); var row = {}; for (var j in attribs) { // Split pair at first equals sign, so "=" can be used in values var pair = attribs[j].split(/=([\s\S]+)?/); // Don't add keys without values if (typeof(pair[1]) !== 'undefined') row[pair[0].trim] = pair[1].trim; }     struc.push(row); }

// Then, pull out the style and display values from the parent module var re1 = new RegExp("\\|[^\\|]*style=([^\\|]+)"); var style = mod.mod.match(re1)[1].trim; var re2 = new RegExp("\\|[^\\|]*display=([^\\|]+)"); var display = mod.mod.match(re2)[1].split(",").map(function(str) { return str.trim; });

// And save everything var vte_project = $("#vte-window").data("vte-project"); obj = { pre: mod.pre, post: mod.post, struc: struc, style: style, display: display, };   vte_project[table] = obj; $("#vte-window").data("vte-project", vte_project); return obj; },

parseUser: function(text) { var m2, m3, user, date; // Try to grab the user from the prior post m2 = text.match(/\[\[User:([^\|\]]+).+(\d{2}:\d{2}, \d+ \S+ \d{4} \(UTC\))/); m3 = text.match(/\[\[User:([^\|\]]+)/); if (m2 !== null) { user = m2[1]; date = m2[2]; } else if (m3 !== null) { user = m3[1]; date = "Unknown"; } else { user = "Unknown"; date = "Unknown"; }   return {user: user, date: date}; }, parseTalkSection: function(section) { var m1, m2, m3, o, text, posts = [], level = 0, index_to = 0; for (var i in section) { // We have a complete post if we're starting a new indent (":"), if we found a user // signature, or if we're the last element of the array m1 = section[i].match(/^(:+)(.*)/); o = vte.parseUser(section[i]); if (m1 !== null) { // Strip the colon from the beginning of the string section[i] = section[i].replace(/^(:+)/, ""); text = section.slice(index_to, (parseInt(i)+1)).join("\n"); index_to = (parseInt(i)+1); level = m1[1].length; o = vte.parseUser(text); posts.push({         msg: text.trim,          user: o.user,          date: o.date,          level: level,        }); } else if (o.user != "Unknown") { text = section.slice(index_to, (parseInt(i)+1)).join("\n"); index_to = (parseInt(i) + 1); posts.push({         msg: text.trim,          user: o.user,          date: o.date,          level: 0,        }); } else if (i == section.length-1) { text = section.slice(index_to, i+1).join("\n"); if (text == "") continue; index_to = (parseInt(i) + 1); o = vte.parseUser(text); posts.push({         msg: text.trim,          user: o.user,          date: o.date,          level: 0,        }); }   }    return posts; }, // parseTalk - Parses a talk page, returns object where key is section heading and value //  is an array of objects. Supports nested conversations. parseTalk: function(data, table) { // Go through the talk page text, build each talk object by section header var lines = data.split("\n"); var obj = {}; var section = []; var title = ""; var p_title = ""; var post = ""; for (var i in lines) { if (! lines[i]) continue; // If this is a new section, add the prior one to the return obj (if it exists) var m;     m = lines[i].match(/^== ?(.+) ?== *$/); if (m !== null && section.length == 0) { title = m[1].trim; } else if (m !== null && section.length > 0) { obj[title] = vte.parseTalkSection(section); title = m[1].trim; section = []; } else { // Otherwise save the section text section.push(lines[i]); }   }    // And add the final section obj[title] = vte.parseTalkSection(section); return obj; },

// Functions to populate the primary vte systems (ie, members, tasks, etc) clickMembers: function { // Update the view cookie vte.setCookie("vte-view", "Members"); // Clear the current content window $("#vte-window-members").html("");

// Emit vte view vte_sock.emit("view", {     name: mw.config.get("wgUserName"),      time: new Date,      page: mw.config.get("wgTitle"),      namespace: mw.config.get("wgNamespaceNumber"),      project: $("#vte-window").data("vte-project").title,      view: "Members"    });

console.log("vte - drawing member content"); // We've already requested the Member content, draw page or wait for content to load var t = setTimeout(function {     var members = $("#vte-window").data("vte-project").members;      if (typeof(members) !== 'undefined' && ! $.isEmptyObject(members)) {       clearInterval(t);        vte.drawMembers;      }    }, 100); }, clickTasks: function { // Update the view cookie vte.setCookie("vte-view", "Tasks"); // Clear the current content window $("#vte-window-tasks").html("");

// Emit vte view vte_sock.emit("view", {     name: mw.config.get("wgUserName"),      time: new Date,      page: mw.config.get("wgTitle"),      namespace: mw.config.get("wgNamespaceNumber"),      project: $("#vte-window").data("vte-project").title,      view: "Tasks"    });

console.log("vte - drawing task content"); // We've already requested the Task content, draw page or wait for content to load var t = setInterval(function {     var tasks = $("#vte-window").data("vte-project").tasks;      if (typeof(tasks) !== 'undefined' && ! $.isEmptyObject(tasks)) {       clearInterval(t);        vte.drawTasks;      }    }, 100); }, clickCommunication: function { // Update the view cookie vte.setCookie("vte-view", "Communication"); // Clear the current content window $("#vte-window-communication").html("");

// Emit vte view vte_sock.emit("view", {     name: mw.config.get("wgUserName"),      time: new Date,      page: mw.config.get("wgTitle"),      namespace: mw.config.get("wgNamespaceNumber"),      project: $("#vte-window").data("vte-project").title,      view: "Communication"    });

console.log("vte - drawing communication content"); var project = $("#vte-window").data("vte-project").title;

// TODO: Load wiki communication

vte.drawCommunication; },

drawMembers: function(data) { // Clear the current content window $("#vte-window-members").html("");

// Grab the member list data var obj = $("#vte-window").data("vte-project").members; var talk = $("#vte-window").data("vte-project").members_talk;

// Add the add member and import members buttons first $("#vte-window-members").append(     " + Add member " +      " + Import members from project network " +      " View:All" + action + " " +      " Sort:Created" + action + " " +      " "    ); // Style the buttons $(".vte-members-create, .vte-members-import").css(s_vteMembersCreate);

// If we don't have any members, prompt to import from project pages if (obj.struc.length == 0) { $("#vte-window-members").append(       " " +        "  This project currently does not have any members listed.  " +        "  Add members by clicking the '+ Add member' link, or import from project-related activity " +        "  by clicking the '+ Import members from project network' link." +        " "      ); // Remove the view and sort dropdowns $("#vte-members-view, #vte-members-sort").remove; $(".vte-members-empty").css(s_vteMembersEmpty); }

// Draw the members table $("#vte-window-members").append(     " "    );

// And display all the current members for (var i in obj.struc) { var n = "name" in obj.struc[i] ? obj.struc[i].name : ""; var proj = "project_edits" in obj.struc[i] ? obj.struc[i].project_edits : ""; var page = "page_edits" in obj.struc[i] ? obj.struc[i].page_edits : ""; var since = "member_since" in obj.struc[i] ? obj.struc[i].member_since : "";

// Attempt to parse date var s_date = vte.parseDateStr(since); var s_str = vte.getMonthText(s_date.getMonth + 1, {abbrev: 1}) + " " + s_date.getDate + ", " + s_date.getFullYear;

// Distinguish between explicit members and activity-based members var explicit = ("activity" in obj.struc[i] && obj.struc[i].activity) ? "vte-member-activity" : "vte-member-explicit";

// Display the row $(".vte-members-table-body").append(       "<tr id=member-" + i + "' class='vte-members-row' vte-member-index='" + i + " " + explicit + "'>" +        "  <td id='vte-members-table-name-" + i + "' class='vte-members-table-name vte-m-td'>" + n + " " +        "  <td id='vte-members-table-since-" + i + "' class='vte-members-table-since vte-m-td'>" + since + " " +        "  <td id='vte-members-table-proj-" + i + "' class='vte-members-table-proj vte-m-td'>" + proj + " " +        "  <td id='vte-members-table-page-" + i + "' class='vte-members-table-page vte-m-td'>" + page + " " +        " "      ); }   // Style the table $(".vte-members-table").css(s_vteMembersTable); $(".vte-m-td").css(s_vteMembersRow); $("#vte-members-view, #vte-members-sort").css(s_vteMembersView); $("#vte-members-view").css(s_vteMembersView);

// Action when clickinig the View or Sort links $("#vte-members-view").click(function(e) {     vte.drawMembersView(e);    }); $("#vte-members-sort").click(function(e) {     vte.drawMembersSort(e);    });

// Highlight row on hover $(".vte-members-row").hover(     function {        $(this).css("background-color", "#EFF5FB");      }, function {        $(this).css("background-color", "#FFFFFF");      }    );

// Action to add a new member $(".vte-members-create").click(function(e) {     // Draw the lightbox      vte.drawMemberEdit;    }); // Action to edit details for an existing user $(".vte-members-row").click(function(e) {     var index = $(e.currentTarget).attr("vte-member-index");      vte.drawMemberEdit(index);    }); // Action to import member from project/page edits $(".vte-members-import").click(function(e) {     vte.getMemberImportData;    }); },

drawMembersView: function(e) { e.stopPropagation; // Draw the View window, supports choosing from All, Activity, or Explicit $("#vte-members-sort-actions").hide; if ($("#vte-members-view-actions").length == 0) { $("#vte-members-view").append(       " " +        "  All " +        "  Activity " +        "  Explicit " +        " "      ); $("#vte-members-view-actions").css(s_vteDropdownList); $(".vte-dropdown-item").css(s_vteDropdownItem); } else { $("#vte-members-view-actions").show; }

// Close the menu if clicking outside of it or hitting escape $("body, .vte-dropdown-item").one("click", function(e) {     e.stopPropogation;      var table = $(".vte-members-table");      if (e.target.id == "vte-members-view-all") {        $(".vte-member-activity").show;        $(".vte-member-explicit").show;      } else if (e.target.id == "vte-members-view-activity") {        $(".vte-member-activity").show;        $(".vte-member-explicit").hide;      } else if (e.target.id == "vte-members-view-explicit") {        $(".vte-member-activity").hide;        $(".vte-member-explicit").show;      }      $("#vte-members-view-actions").hide;    }); $(document).on("keyup.hide_actions", function(e) {     if (e.keyCode == 27) {        $("#vte-members-view-actions").hide;        $(document).unbind("keyup.hide_actions");      }    }); },

drawMembersSort: function(e) { e.stopPropagation; // Draw the sort window, supports sorting by name, since, project edits, page edits, etc $("#vte-members-view-actions").hide; if ($("#vte-members-sort-actions").length == 0) { $("#vte-members-sort").append(       " " +        "  Name " +        "  Member Since " +        "  Project Edits " +        "  Page Edits " +        " "      ); $("#vte-members-sort-actions").css(s_vteDropdownList); $(".vte-dropdown-item").css(s_vteDropdownItem); } else { $("#vte-members-sort-actions").show; }

// Close the menu if clicking outside of it or hitting escape $("body, .vte-dropdown-item").one("click", function(e) {     e.stopPropagation;      var table = $(".vte-members-table");      if (e.target.id == "vte-members-sort-name") {        var rows = table.find('tr').toArray.sort(vte.comparer(0));        // Determine if we're ascending or descending        $(".vte-members-table").data("name", !$(".vte-members-table").data("name"));        if (!$(".vte-members-table").data("name")) rows = rows.reverse;        for (var i = 0; i < rows.length; i++) { table.append(rows[i]); }        $("#vte-members-sort").html("Sort:Name" + action);      } else if (e.target.id == "vte-members-sort-since") {        var rows = table.find('tr').toArray.sort(vte.comparer(1));        $(".vte-members-table").data("since", !$(".vte-members-table").data("since"));        if (!$(".vte-members-table").data("since")) rows = rows.reverse;        for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-members-sort").html("Sort:Member Since" + action); } else if (e.target.id == "vte-members-sort-proj") { var rows = table.find('tr').toArray.sort(vte.comparer(2)); $(".vte-members-table").data("proj", !$(".vte-members-table").data("proj")); if (!$(".vte-members-table").data("since")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-members-sort").html("Sort:Project Edits" + action); } else if (e.target.id == "vte-members-sort-page") { var rows = table.find('tr').toArray.sort(vte.comparer(3)); $(".vte-members-table").data("page", !$(".vte-members-table").data("page")); if (!$(".vte-members-table").data("page")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-members-sort").html("Sort:Page Edits" + action); }     $("#vte-members-sort-actions").hide; });   $(document).on('keyup.hide_actions', function(e) { if (e.keyCode == 27) { $("#vte-members-sort-actions").hide; $(document).unbind("keyup.hide_actions"); }   });  },

drawMemberEdit: function(index) { console.log("in drawMemberEdit"); },

getMemberImportData: function { console.log("in getMemberImportData"); // Grab any potential current member data var vte_project = $("#vte-window").data("vte-project");

// Draw the import lightbox $("#vte-window").append(     " " +      "  " +      "    <img src='https://upload.wikimedia.org/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'>" +      "  " +      "  <input type='submit class='vte-import-save' value='Import' />" +      "  " +      "  Loading user links on project pages... " +      "  Loading edits to project pages... " +      "  Loading project pages... " +      "  Loading edits by top project editors... " +      "   " +      " "    ); // Style the lightbox $("#vte-member-import").css(s_vteMemberImport) $("#vte-import-loading-links, #vte-import-loading-proj, #vte-import-loading-page, #vte-import-loading-edits").css(s_vteMemberImportLoading);

// Handle the loading text if we're still requesting the data, located in vte_project.members_network var elapsed = 0; var t = setInterval(function {     if ("members_network" in vte_project) {        clearInterval(t);        vte.drawMemberImport;      } else {        // Check for timeout        if (elapsed >= 60000) {          clearInterval(t);          console.error("Timed out requesting project member data");        }      }      elapsed += 100;    }, 100); },

drawMemberImport: function(data) { console.log("In drawMemberImport"); // Data will contain {editors: [], links: {}, p_pages: {}, pages: []} in vte_project.members_network var vte_project = $("#vte-window").data("vte-project"); var network = vte_project.members_network; var members = vte_project.members.struc; $(".vte-loading").remove;

// Structure the network data - all project edits will be included, and all users with links // on project pages. We can ignore network.[p_pages|pages] at this point.

// Then, add a row for each potential project member showing name, project edit sparkline, // invitation button, etc

},

drawTasks: function(data) { // Clear the current content window $("#vte-window-tasks").html("");

// Grab the task list data var obj = $("#vte-window").data("vte-project").tasks; var talk = $("#vte-window").data("vte-project").tasks_talk;

// Projects have the option to include anything in the tasks table, but for the // VTE we'll want to display the title, created, due, priority, and owner. In   // the task details we'll additionally display subtasks, burndown, etc.

// Add the create task button first $("#vte-window-tasks").append(     " + Add task " +      " View:All" + action + " " +      " Sort:Created" + action + " " +      " "    ); $("#vte-tasks-view, #vte-tasks-sort").css(s_vteTasksView);

// If we don't have any tasks, prompt to create a new one if (obj.struc.length == 0) { $("#vte-window-tasks").append(       " " +        "  This project currently does not have any tasks listed.  " +        "  Add tasks by clicking the '+ Add task' link." +        " "      ); // Remove the view and sort dropdowns $("#vte-tasks-view, #vte-members-sort").remove; $(".vte-tasks-empty").css(s_vteTasksEmpty); }

// Will display created date, priority, title, number of comments, and owner // Created color will be based on date since creation // Priority color will be based on priority (either high/medium/low or 1/2/3) // Font color will be based on whether the task is completed $("#vte-window-tasks").append(     " "    );

var closed = 0; var open = 0; for (var i in obj.struc) { var t = "title" in obj.struc[i] ? obj.struc[i].title : ""; var c = "created" in obj.struc[i] ? obj.struc[i].created : ""; var d = "due" in obj.struc[i] ? obj.struc[i].due : ""; var p = "priority" in obj.struc[i] ? obj.struc[i].priority : ""; var o = "owner" in obj.struc[i] ? obj.struc[i].owner : "";

var com = t in talk ? talk[t].length : 0;

// Attempt to parse dates var c_date = vte.parseDateStr(c); var c_str = vte.getMonthText(c_date.getMonth + 1, {abbrev: 1}) + " " + c_date.getDate + ", " + c_date.getFullYear; var n_date = new Date; var n_str = vte.getMonthText(n_date.getMonth + 1, {abbrev: 1}) + " " + n_date.getDate + ", " + n_date.getFullYear;

var d_date = vte.parseDateStr(d); var d_str = d_date ? vte.getMonthText(d_date.getMonth + 1, {abbrev: 1}) + " " + d_date.getDate + ", " + d_date.getFullYear : d;

// Whether the task was completed var comp = ("completed" in obj.struc[i] && obj.struc[i].completed) ? "vte-task-completed" : "vte-task-open";

// Color of the creation date will be red for older open tasks, going towards black for // newer tasks. Color progression will be for each week going back one month (ie, tasks     // created in the last week will be black, two weeks ago will be slightly red, etc). // If we have a due date for this task, color will still go from black to red, but color // steps will be between the current date and creation date and due date (ie, background     // color will get more red the closer we are to the due date, split into four equal time increments). // If the due date passed, the color will be red. var c_color; if (d_date) { var inc = (d_date.getTime - c_date.getTime) / 4; var spent = n_date.getTime - c_date.getTime; if (d_date.getTime < n_date.getTime) { c_color = "#FF0400"; } else if (Math.ceil(spent / inc) == 4) { c_color = "#FF0400"; } else if (Math.ceil(spent / inc) == 3) { c_color = "#BA0300"; } else if (Math.ceil(spent / inc) == 2) { c_color = "#590200"; } else { c_color = "#000000"; }     } else { var w = 1000 * 60 * 60 * 24 * 7; if (n_date.getTime - c_date.getTime > w * 3) { c_color = "#FF0400"; } else if (n_date.getTime - c_date.getTime > w * 2) { c_color = "#BA0300"; } else if (n_date.getTime - c_date.getTime > w) { c_color = "#590200"; } else { c_color = "#000000"; }     }      // Or, if we've already completed the task created background should just be black if (comp == "vte-task-completed") c_color = "#000000";

//c = vte.getDateStr( vte.parseDateStr(c) ); //d = vte.getDateStr( vte.parseDateStr(d) ); // Parse any wikitext in the title //t = wiky.process( t ); // Didn't work t = InstaView.convert( t ).slice(3); // Removing first 4 characters, InstaView adds to everything. // Display the row $(".vte-tasks-table-body").append(       "<tr id='task-" + i + "' class='vte-tasks-row " + comp + "' vte-task-index='" + i + "'>" +        "  <td id='vte-tasks-table-created-" + i + "' class='vte-tasks-table-created vte-t-td' style='background-color: " + c_color + "; color: #FFF'>" + c_str + " " +        "  <td id='vte-tasks-table-priority-" + i + "' class='vte-tasks-table-priority vte-t-td'>" + p + " " +        "  <td id='vte-tasks-table-title-" + i + "' class='vte-tasks-table-title vte-t-td'>" + t + " " +        "  <td id='vte-tasks-table-comments-" + i + "' class='vte-tasks-table-comments vte-t-td'>" + com + " comments " +        "  <td id='vte-tasks-table-owner-" + i + "' class='vte-tasks-table-owner vte-t-td'>" + o + " " +        " "      ); }   // Style the tables $(".vte-tasks-table").css(s_vteTasksTable); $(".vte-t-td").css(s_vteTasksRow); $(".vte-task-completed").css(s_vteTaskCompleted); $(".vte-tasks-table-title").css(s_vteTasksTableTitle); $(".vte-tasks-table-priority").css(s_vteTasksTablePriority); $(".vte-tasks-table-created").css(s_vteTasksTableCreated); $(".vte-tasks-table-comments").css(s_vteTasksTableComments); $(".vte-tasks-table-owner").css(s_vteTasksTableOwner); $(".vte-tasks-table-due").css(s_vteTasksTableDue); $(".oh, .ch").css({ "cursor": "pointer", "padding": "4px 0px" }); $(".vte-tasks-create").css(s_vteTasksCreate);

// Action when clicking the View or Sort links $("#vte-tasks-view").click(function(e) {     vte.drawTasksView(e);    }); $("#vte-tasks-sort").click(function(e) {     vte.drawTasksSort(e);    });

// Action to highlight row on hover $(".vte-tasks-row").hover(     function {        $(this).css("background-color", "#EFF5FB");      }, function {        $(this).css("background-color", "#FFFFFF");      }    );

// Make the table sortable by clicking the headers $('.oh, .ch').click(function {     $(".oh, .ch").css("background-color", "#FFFFFF");      $( this ).css("background-color", "#F2F2F2");      var table = $(this).parents('table').eq(0);      var rows = table.find('tr:gt(0)').toArray.sort(vte.comparer($(this).index));      this.asc = !this.asc;      if (!this.asc) rows = rows.reverse;      for (var i = 0; i < rows.length; i++) { table.append(rows[i]); }    });

// Action to add new task //$(".vte-tasks-create").button.click(function(e) {   $(".vte-tasks-create").click(function(e) { e.preventDefault; // Draw the lightbox vte.drawTaskEdit; }); // END submit new task

// Action to edit an existing task $(".vte-tasks-row").click(function(e) {     var index = $(e.currentTarget).attr("vte-task-index");      vte.drawTaskEdit(index);    }); },  drawTasksView: function(e) { e.stopPropagation; // Draw the View window, supports choosing from All, Open, or Closed $("#vte-tasks-sort-actions").hide; if ($("#vte-tasks-view-actions").length == 0) { $("#vte-tasks-view").append(       " " +        "  All " +        "  Open " +        "  Closed " +        " "      ); $("#vte-tasks-view-actions").css(s_vteDropdownList); $(".vte-dropdown-item").css(s_vteDropdownItem); } else { $("#vte-tasks-view-actions").show; }   // Close the menu if clicking outside of it or hitting escape $("body, #vte-tasks-view-actions").one("click", function(e) {     e.stopPropagation;      if (e.target.id == "vte-tasks-view-all") {        $(".vte-task-open").show;        $(".vte-task-completed").show;      } else if (e.target.id == "vte-tasks-view-open") {        $(".vte-task-open").show;        $(".vte-task-completed").hide;      } else if (e.target.id == "vte-tasks-view-closed") {        $(".vte-task-open").hide;        $(".vte-task-completed").show;      }      $("#vte-tasks-view-actions").hide;    }); $(document).on('keyup.hide_actions', function(e) {     if (e.keyCode == 27) {        $("#vte-tasks-view-actions").hide;        $(document).unbind('keyup.hide_actions');      }    }); }, drawTasksSort: function(e) { e.stopPropagation; // Draw the Sort window, supports sorting by Created date, priority, title, comments, owner, etc $("#vte-tasks-view-actions").hide; if ($("#vte-tasks-sort-actions").length == 0) { $("#vte-tasks-sort").append(       " " +        "  Created " +        "  Priority " +        "  Title " +        "  Comments " +        "  Owner " +        " "      ); $("#vte-tasks-sort-actions").css(s_vteDropdownList); $(".vte-dropdown-item").css(s_vteDropdownItem); } else { $("#vte-tasks-sort-actions").show; }

// Close the menu if clicking outside of it or hitting escape $("body, .vte-dropdown-item").one("click", function(e) {     e.stopPropagation;      var table = $(".vte-tasks-table");      if (e.target.id == "vte-tasks-sort-created") {        console.log("Sorting created");        var rows = table.find('tr').toArray.sort(vte.comparer(0));        // Determine if we're ascending/descending        $(".vte-tasks-table").data("created", !$(".vte-tasks-table").data("created"));        if (!$(".vte-tasks-table").data("created")) rows = rows.reverse;        for (var i = 0; i < rows.length; i++) { table.append(rows[i]); }        $("#vte-tasks-sort").html("Sort:Created" + action);      } else if (e.target.id == "vte-tasks-sort-priority") {        console.log("Sorting priority");        var rows = table.find('tr').toArray.sort(vte.comparer(1));        $(".vte-tasks-table").data("priority", !$(".vte-tasks-table").data("priority"));        if (!$(".vte-tasks-table").data("priority")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-tasks-sort").html("Sort:Priority" + action); } else if (e.target.id == "vte-tasks-sort-title") { console.log("Sorting title"); var rows = table.find('tr').toArray.sort(vte.comparer(2)); $(".vte-tasks-table").data("title", !$(".vte-tasks-table").data("title")); if (!$(".vte-tasks-table").data("title")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-tasks-sort").html("Sort:Title" + action); } else if (e.target.id == "vte-tasks-sort-comments") { console.log("Sorting comments"); var rows = table.find('tr').toArray.sort(vte.comparer(3)); $(".vte-tasks-table").data("comments", !$(".vte-tasks-table").data("comments")); if (!$(".vte-tasks-table").data("comments")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-tasks-sort").html("Sort:Comments" + action); } else if (e.target.id == "vte-tasks-sort-owner") { console.log("Sorting owner"); var rows = table.find('tr').toArray.sort(vte.comparer(4)); $(".vte-tasks-table").data("owner", !$(".vte-tasks-table").data("owner")); if (!$(".vte-tasks-table").data("owner")) rows = rows.reverse; for (var i = 0; i < rows.length; i++) { table.append(rows[i]); } $("#vte-tasks-sort").html("Sort:Owner" + action); }     $("#vte-tasks-sort-actions").hide; });   $(document).on('keyup.hide_actions', function(e) { if (e.keyCode == 27) { $("#vte-tasks-sort-actions").hide; $(document).unbind('keyup.hide_actions'); }   });  },  populateChat: function {    var project = typeof($("#vte-window").data("vte-project")) !== 'undefined' ?       $("#vte-window").data("vte-project").title : "";    // Draw the chat form    $("#vte-window-left-chat").append( " " +     "  <ul id='vte-communication-chat-messages' />" + " " +      "    <form id='vte-communication-chat-form' action=''>" + "     <input id='vte-communication-chat-input' autocomplete='off'/>" + "     <input type='submit' class='vte-communication-chat-send' value='Send' />" + "   " +      "  " +      " "    );

// Style the chat window $(".vte-communication-chat-send").button.css(s_vteCommunicationChatSend); $("#vte-communication-chat").css(s_vteCommunicationChat); $("#vte-communication-chat").css("width", $("#vte-window-left-chat").width + "px"); $("#vte-communication-chat-messages").css("max-height", ($("#vte-window-left-chat").height / 2) + "px"); $("#vte-communication-chat-input").css(s_vteCommunicationChatInput); $("#vte-communication-chat-messages").css(s_vteCommunicationChatMessages);

// Load the chat client $("#vte-communication-chat-form").submit(function {     if ($("#vte-communication-chat-input").val) {        vte_sock.emit("chat", { name: mw.config.get("wgUserName"), time: new Date, project: project, message: $("#vte-communication-chat-input").val, });     }

$("#vte-communication-chat-input").val(""); return false; });   vte_sock.on("chat", function(obj) { // TODO: Potentially only show chat messages from users in this project?? $("#vte-communication-chat-messages").append(       "<li class='vte-communication-chat-line'>" +        "  " + obj.name + ": " +        "  " + obj.message + " " +        "</li>"      ); // Make sure we're scrolled to the bottom $("#vte-communication-chat-messages").scrollTop( $("#vte-communication-chat-messages")[0].scrollHeight ); // Style the message $(".vte-communication-chat-line").css(s_vteCommunicationChatLine); $(".vte-communication-chat-user").css(s_vteCommunicationChatUser); $(".vte-communication-chat-message").css(s_vteCommunicationChatMessage); });

}, drawCommunication: function(data) { var project = $("#vte-window").data("vte-project").title;

// Clear the current content window and draw the chat form $("#vte-window-communication").html("WIP - Communication system"); },

// drawTaskEdit: Draws the task edit lightbox. Will prepopulate with task info if an existing //  task was clicked, otherwise will draw the empty box to create a new task. drawTaskEdit: function(index) { var obj = $("#vte-window").data("vte-project").tasks; var task = {}; var complete_button = ""; // If we're given an index, pull out the data for that task if (typeof(index) !== 'undefined') { task = obj.struc[index]; complete_button = "<input type='submit' class='vte-task-mark-complete' value='Mark Complete' index='" + index + "'/>"; }   // Make sure task has required fields if (!("title" in task)) task.title = ""; if (!("page" in task)) task.page = ""; if (!("priority" in task)) task.priority = ""; if (!("remaining" in task)) task.remaining = ""; if (!("due" in task)) task.due = ""; if (!("notes" in task)) task.notes = ""; if (!("owner" in task)) task.owner = "";

// Draw the lightbox $("#vte-window").append(     " " +      "  " +      "    <img src='https://upload.wikimedia.org/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'>" +      "  " +      "  <input type='submit' class='vte-task-save' value='Save' />" +      complete_button +      "  " +      "   " +      "      " +      "    " +      "  " +      "  " +      "  " +      "    Assigned To: " +      "    " +      "    Sub Tasks: " +      "    " +      "  " +       "  " +      "    " +      "    Comments/Details " +      "    " +      "      <textarea id='vte-task-notes' rows='5' cols='40'>" + task.notes + " " +      "    " +      "  " +      " "    ); // Style inputs var t = setTimeout(function {     $("#vte-task-title").width(($("#vte-task-edit").width - $("#vte-task-page-label").width - 100) + "px");      $("#vte-task-page").width(($("#vte-task-edit").width - $("#vte-task-page-label").width - 100) + "px");    }, 50);

// Add in subtasks, owners, notes, etc, if they exist // Owners - var owners = "owner" in task ? task.owner.split(",").map( function(str) { return str.trim; } ) : []; for (var i in owners) { if (owners[i] == "") continue; $(".vte-task-edit-owners").append(       " " +        "  <input type='submit' vte-owner-index='" + i + "' class='vte-task-edit-remove-owner' value='-' />" +        "  <div class='vte-task-edit-owner' vte-owner-index='" + i + "'>" + owners[i] + " " +        " "      ); }   // And then add the owner's edit field $(".vte-task-edit-owners").append(     " " +      "  <input type='text' id='vte-task-owner' value='' />" +      " " +      "<input type='submit' class='vte-task-edit-add-owner' value='Add' />"    );

// And check for any/all subtasks var i = 0; while ("subtask" + i in task) { $(".vte-task-edit-subtasks").append(       " " +        "  <input type='checkbox' index='" + i + "' class='vte-task-edit-subcomplete' />" +        "  <div class='vte-task-edit-subtask' index='" + i + "'>" + task["subtask" + i] + " " +        " "      ); if ("subcomplete" + i in task && task["subcomplete" + i]) $("#vte-task-edit-subtask-" + i).prop("checked", true); i += 1; }   // And then add the subtasks edit field $(".vte-task-edit-subtasks").append(     " " +      "  <input type='text' id='vte-task-subtask' value='' />" +      " " +      "<input type='submit' class='vte-task-edit-add-subtask' value='+' />"    );

// Draw the burndown graph (if we have "remaining" updates) or user edit graph (if we have "owners") // TODO: This will require getting multiple revisions of the Tasks page

// Style the box $("#vte-task-edit").css(s_vteTaskEdit); $(".vte-task-mark-complete").css(s_vteTaskMarkComplete); $(".vte-task-save").css(s_vteTaskSave); $("#vte-task-close").css(s_vteTaskClose); $(".vte-task-edit-label").css(s_vteTaskEditLabel); $(".vte-task-edit-input").css(s_vteTaskEditInput); $(".vte-task-edit-left").css(s_vteTaskEditLeft); $(".vte-task-edit-right").css(s_vteTaskEditRight); $(".vte-task-edit-owners").css(s_vteTaskEditOwners); $(".vte-task-edit-owner").css(s_vteTaskEditOwner); $(".vte-task-edit-remove-owner").css(s_vteTaskEditRemoveOwner); $(".vte-task-edit-add-owner").css(s_vteTaskEditAddOwner); $(".vte-task-edit-subtasks").css(s_vteTaskEditSubtasks); $(".vte-task-edit-add-subtask").css(s_vteTaskEditAddSubtask); $(".vte-task-edit-subtask").css(s_vteTaskEditSubtask); $(".vte-task-edit-subcomplete").css(s_vteTaskEditSubcomplete); $(".vte-task-edit-graph").css(s_vteTaskEditGraph); $(".vte-task-edit-notes").css(s_vteTaskEditNotes); $(".vte-owner-row").css(s_vteOwnerRow); $(".vte-subtask-row").css(s_vteSubtaskRow); $("#vte-task-edit input[type='submit']").css("font-size", "10px");

// All the actions (not using closures so we have access to variables in calling scope -   // see http://stackoverflow.com/questions/10204420/define-function-within-another-function-in-javascript) function addOwner { var index = "owner" in task ? task.owner.split(",").length : 0; var $html = $(       "<div class='vte-owner-row' vte-owner-index='" + index + "'>" +        "  <input type='submit' vte-owner-index='" + index + "' class='vte-task-edit-remove-owner' value='-'/>" +        "  <div class='vte-task-edit-owner' vte-owner-index='" + index + "'>" +              $("#vte-task-owner").val +         "  "+        " "      ); $("#vte-task-owner").val(""); $("#vte-task-edit-owner-input").before($html); $(".vte-task-edit-remove-owner").button.click(removeOwner); $(".vte-task-edit-owner").css(s_vteTaskEditOwner); $(".vte-task-edit-remove-owner").css(s_vteTaskEditRemoveOwner); $(".vte-owner-row").css(s_vteOwnerRow); $("#vte-task-edit input[type='submit']").css("font-size", "10px"); }   function removeOwner(e) { var index = $(e.currentTarget).attr("vte-owner-index"); task.owner.split(",").splice(index, 1); $("[vte-owner-index='" + index + "']").remove; }   function addSubtask { // find the next subtask index var index = 0; while ("subtask" + index in task) index += 1; var $html = $(       "<div class='vte-subtask-row' vte-subtask-index='" + index + "'>" +        "  <input type='checkbox' index='" + index + "' class='vte-task-edit-subcomplete' />" +        "  <div class='vte-task-edit-subtask' index='" + index + "'>" + $("#vte-task-subtask").val + " " +        " "      ); task["subtask" + index] = $("#vte-task-subtask").val; $("#vte-task-subtask").val(""); $("#vte-task-edit-subtask-input").before($html); $(".vte-task-edit-subtask").css(s_vteTaskEditSubtask); $(".vte-task-edit-subcomplete").css(s_vteTaskEditSubcomplete); $(".vte-subtask-row").css(s_vteSubtaskRow); $("#vte-task-edit input[type='submit']").css("font-size", "10px"); }

// Handle the add owner action $(".vte-task-edit-add-owner").button.click(addOwner); // Handle the remove owner action $(".vte-task-edit-remove-owner").button.click(removeOwner);

// Handle the add subtask action $(".vte-task-edit-add-subtask").button.click(addSubtask); // Nothing needed to complete the subtask - we'll check the checkbox for each task on save

// Handle the mark complete and save actions $(".vte-task-mark-complete, .vte-task-save").button.click(function(e) {     e.preventDefault;      // Task title is required      if (! $("#vte-task-title").val) {       mw.notify("You must enter a Task Title before saving the task.");        console.warn("vte: You must enter a Task Title before saving the task.");        return false;      }      var obj = $("#vte-window").data("vte-project").tasks;

// Update the completed date if we've clicked Mark Complete var index = $(e.currentTarget).attr("index"); if ($(e.currentTarget).attr("value") == "Mark Complete") { var d = new Date; obj.struc[index].completed = vte.getDateStr; }     // Add the created time if this is a new task if (typeof(index) === 'undefined') { task.created = vte.getWikiDateStr; obj.struc.push(task); index = obj.struc.length-1; }     // Update the task object with the other values obj.struc[index].title = $("#vte-task-title").val; obj.struc[index].page = $("#vte-task-page").val; obj.struc[index].priority = $("#vte-task-priority").val; obj.struc[index].remaining = $("#vte-task-remaining").val; obj.struc[index].due = $("#vte-task-due").val; obj.struc[index].notes = $("#vte-task-notes").val; var owner = []; $(".vte-task-edit-owner").each(function {       owner.push($(this).html.trim);      }); obj.struc[index].owner = owner.join(","); $(".vte-task-edit-subtask").each(function {       obj.struc[index]["subtask" + $(this).attr("index")] = $(this).html;      }); $(".vte-task-edit-subcomplete").each(function {       obj.struc[index]["subcomplete" + $(this).attr("index")] = $(this).prop("checked") == true ? 1 : 0;      });

// Save the struc and call the update function for both the tasks page and the tasks talk page var vte_project = $("#vte-window").data("vte-project"); vte_project.tasks = obj; vte_project.tasks_talk[ $("#vte-task-title").val ] = []; var complete = {task: 0, talk: 0}; vte.updateTaskData(function {       complete.task = 1;        console.log("Successfully updated details for task: " + $("#vte-task-title").val);        mw.notify( "Successfully updated task: " + $("#vte-task-title").val + "." );     }, function(xhr) {        complete.task = 1;        console.error("Failed to update details for task: " + JSON.stringify(xhr));        mw.notify( "Failed to update details for task: " + JSON.stringify(xhr));      }); vte.updateTaskTalkData(function {       complete.talk = 1;        console.log("Successfully updated talk page for task: " + $("#vte-task-title").val);      }, function(xhr) {        complete.talk = 1;        console.error("Failed to update talk page for task: " + JSON.stringify(xhr));      }); var timeout = 0; var t1 = setInterval(function {       timeout += 100;        if (complete.task == 1 && complete.talk == 1) {          clearInterval(t1);          $("#vte-tasks").click;          $("#vte-task-edit").remove;        }        if (timeout >= 10000) {          clearInterval(t1);          console.error("Timed out attempting to save Tasks and Tasks Talk pages: " + JSON.stringify(complete));        }      }, 100);

});   // Handle the close action    $("#vte-task-close").click(function { $("#vte-task-edit").remove; });

},

// drawUserEdits: Will structure and graph user edits over time, separated by namespace drawUserEdits: function(data) { // Structure the data for the graph var sw = ew = vte.convertDateToWikiWeek; for (var i in data["result"]) if (data["result"][i].rc_wikiweek < sw) sw = data["result"][i].rc_wikiweek; var edits = Array(ew - sw); for (var i = 0; i < edits.length; i++) edits[i] = { date: vte.convertWikiWeekToDate(i), 0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:0, 13:0, 14:0, 15:0   };    var y_max = 0; for (var i in data["result"]) { edits[ data["result"][i].rc_wikiweek - sw ][ data["result"][i].rc_page_namespace ] += data["result"][i]["rc_edits"]; if (data["result"][i].rc_edits > y_max) y_max = data["result"][i].rc_edits; }

// Draw with d3   var margin = {top: 20, right: 80, bottom: 50, left: 50}; var w = $("#vte-members-contribution").width - margin.left - margin.right - 20, h = 230 - margin.top - margin.bottom; var parseDate = d3.time.format("%Y%m%d").parse; var x = d3.time.scale .range([0, w]); var y = d3.scale.linear .range([h, 0]); var color = d3.scale.category10; var xAxis = d3.svg.axis .scale(x) .orient("bottom"); var yAxis = d3.svg.axis .scale(y) .orient("left"); var line = d3.svg.line .interpolate("basis") .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.count); }); //.attr("shape-rendering", "crispEdges");

var svg = d3.select("#vte-members-contribution-edits").append("svg") .attr("width", w + margin.left + margin.right) .attr("height", h + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

color.domain(d3.keys(edits[0]).filter( function(key) { return key !== "date"; })); edits.forEach(function(d) {     d.date = parseDate(d.date);    }); var namespaces = color.domain.map(function(ns) {     return {        namespace: vte.convertIdToNamespace(ns),        values: edits.map(function(d) { return {date: d.date, count: +d[ns]}; })     };    });    x.domain(d3.extent(edits, function(d) { return d.date; })); y.domain([     d3.min(namespaces, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),      d3.max(namespaces, function(c) { return d3.max(c.values, function(v) { return v.count; }); })    ]);

svg.append("g") .style("fill", "none") .style("stroke", "#000") .style("shape-rendering", "crispEdges") .attr("transform", "translate(0," + h + ")") .call(xAxis); svg.append("g") .style("fill", "none") .style("stroke", "#000") .style("shape-rendering", "crispEdges") .call(yAxis) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", ".70em") .style("text-anchor", "end") .text("Edits");

var ns = svg.selectAll(".ns") .data(namespaces) .enter.append("g") .attr("class", "ns"); ns.append("path") .style("fill", "none") .style("stroke", "steelblue") .style("stroke-width", "1.5px") .attr("d", function(d) { return line(d.values); }) .style("stroke", function(d) { return color(d.namespace); }); ns.append("text") .datum(function(d) {          // Return null string if the last value was 0 (to avoid overlap)          if (d.values[d.values.length -1].count == 0) {            return { namespace: "", value: d.values[d.values.length - 1] };          } else {            return { namespace: d.namespace, value: d.values[d.values.length - 1]};           }        }) .attr("transform", function(d) {          return "translate(" + x(d.value.date) + "," + y(d.value.count) + ")";         }) .attr("x", 3) .attr("dy", ".35em") .text(function(d) { return d.namespace; }); // And then fix the labels on the axes (needed since the ticks and text are defined at the same time above) $("#vte-members-contribution-edits > svg text").css({"stroke": "none", "fill": "#000"}); },

// // HELPER FUNCTIONS //

// processWikiText - Converts WikiText to valid HTML - This could be done in a call to  //   the MediaWiki API, but that seems like it would be less efficient for the many //  small cases we would require it for (i.e., making an API request for every Title  //   and Description field for each Task for a given project). //  Ie, Mediawiki API - https://www.mediawiki.org/wiki/API:Parsing_wikitext // Currently using InstaView as Wiky didn't suit what we needed, so this may be unnecessary. processWikiText: function(str) { },

// Helper functions to sort table by clicking on the header // (see http://stackoverflow.com/questions/3160277/jquery-table-sort) comparer: function(index) { return function(a, b) { var valA = vte.getCellValue(a, index), valB = vte.getCellValue(b, index) return $.isNumeric(valA) && $.isNumeric(valB) ? valA - valB : valA.localeCompare(valB) } },   getCellValue: function(row, index) { return $(row).children('td').eq(index).html; },

// parseDateStr - Given a string, will attempt to parse and create a date object parseDateStr: function(str) { if (typeof(str) === 'undefined') return new Date; var m=null; m = str.match(/(\d+):(\d+), (\d+) (\S+) (\d+)/); if (m !== null) return new Date(m[5] + "-" + vte.getMonth(m[4]) + "-" + m[3] + " " + m[1] + ":" + m[2] + ":00"); // Any additional string formats we want to check?

// Try and parse the string var d = new Date(str); if (isNaN(d.getTime)) { return str; } else { return d;   } }, // Checks to see if the supplied argument is a valid date string isValidDate: function(d) { if ( Object.prototype.toString.call(d) !== "[object Date]" ) return false; return !isNaN(d.getTime); }, // getDateStr - Given a date object, returns a string like YYYY/mm/dd hh:mm:ss. If no date // is given will return the string for the current time. If the date object isn't valid, // just returns the supplied argument. getDateStr: function(d) { if (typeof(d) === 'undefined') { d = new Date; }   if (! vte.isValidDate(d)) return d;    return String(d.getFullYear) + "/" + String(vte.pad( parseInt(d.getMonth) + 1, 2)) + "/" + String(vte.pad(d.getDate, 2)) + " " + String(vte.pad(d.getHours, 2)) + ":" + String(vte.pad(d.getMinutes, 2)) + ":" + String(vte.pad(d.getSeconds, 2)); }, // getWikiDateStr - Given a date object, returns a wiki-fied date string (the same  // format that is saved if users enter, ie, "13:15, 14 October 2014 (UTC)") getWikiDateStr: function(d) { if (typeof(d) === 'undefined') { d = new Date; }   return String(vte.pad(d.getUTCHours, 2)) + ":" + String(vte.pad(d.getUTCMinutes, 2)) + ", " + String(d.getUTCDate) + " " + vte.getMonthText(d.getUTCMonth + 1) + " " + String(d.getUTCFullYear) + " (UTC)"; },

getMonth: function(m) { var months = { "January": 1, "February": 2, "March": 3, "April": 4, "May": 5, "June": 6, "July": 7, "August": 8, "September": 9, "October": 10, "November": 11, "December": 12 };   if (! (m in months)) { console.error("Invalid month: " + m); }   return months[m]; }, getMonthText: function(m, opt) { if (typeof opt === 'undefined') opt = {}; var months = {}; if ("abbrev" in opt && opt.abbrev) { months = {1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun", 7: "Jul", 8: "Aug", 9: "Sept", 10: "Oct", 11: "Nov", 12: "Dec" };   } else { months = {1: "January", 2: "February", 3: "March", 4: "April", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December" };   }    if (! (m in months)) { console.error("Invalid month number: " + m); }   return months[m]; },

// convertDateToWikiWeek - helper function to convert a date of the form YYYYmmdd to wikiweek convertDateToWikiWeek: function(d) { if (typeof(d) === 'undefined') { var date = new Date; d = String(date.getFullYear) + String(vte.pad( parseInt(date.getMonth) + 1, 2)) + String(vte.pad(date.getDate, 2)); }   var ms = new Date(d.substring(0,4) + '/' + d.substring(4,6) + '/' + d.substring(6,8) + ' 00:00:00').getTime; var originMs = new Date('2001/01/01 00:00:00').getTime; var msDiff = ms - originMs; // milliseconds in a week var week = 7 * 24 * 60 * 60 * 1000; // weeks in the millisecond range return Math.floor(msDiff / week); }, pad: function(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; }, convertWikiWeekToDate: function(ww) { // milliseconds in wiki weeks var ms = ww * 7 * 24 * 60 * 60 * 1000; // Add milliseconds since the epoch to week ms value var mil = new Date('2001/01/01 00:00:00').getTime + ms; var date = new Date(mil);

// Date will be of form YYYYmmdd return String(date.getFullYear) + String(vte.pad( parseInt(date.getMonth) + 1, 2)) + String(vte.pad(date.getDate, 2)); }, convertIdToNamespace: function(id) { var ns = { 0: "Article", 1: "Article talk", 2: "User", 3: "User talk", 4: "Wikipedia", 5: "Wikipedia talk", 6: "File", 7: "File talk", 8: "MediaWiki", 9: "MediaWiki talk", 10: "Template", 11: "Template talk", 12: "Help", 13: "Help talk", 14: "Category", 15: "Category talk", 100: "Portal", 101: "Portal talk", 108: "Book", 109: "Book talk", 118: "Draft", 119: "Draft talk" };   return ns[id]; }, getNamespaceColor: function(ns) { // If the namespace is an int convert it to text if (! isNaN(ns)) { ns = vte.convertIdToNamespace(ns); }

var un = "#424242"; var ns_color = { "Article": "#CC0000", "Article talk": "#F7B7B7", "User": "#5C8D20", "User talk": "#85ED82", "Wikipedia": "#2E97E0", "Wikipedia talk": "#B9E3F9", "File": "#E1711D", "File talk": "#FFC04C", "MediaWiki": un, "MediaWiki talk": "#5555FF", "Template": "#55FFFF", "Template talk": "#0000C0", "Help": "#008800", "Help talk": "#00C0C0", "Category": "#FFAFAF", "Category talk": "#808080", "Portal": "#75A3D1", "Portal talk": "#A679D2", "Book": "#94EF2B", "Book talk": un, "Draft": "#99FFFF", "Draft talk": "#99BBFF" };   return ns_color[ns]; }, isJson: function(str) { try { JSON.parse(str); } catch (e) { return false; }   return true; }, setCookie: function(key, value, options) { if (typeof(options) === 'undefined') options = {}; // Set defaults if (! ("expires" in options)) options.expires = 7; if (! ("path" in options)) options.path = "/"; // Then set the cookie value = typeof(value) === 'object' ? JSON.stringify(value) : value; $.cookie(key, value, options); }, getCookie: function(key) { var value = $.cookie(key); return vte.isJson(value) ? JSON.parse(value) : value; }, removeCookie: function(key) { $.cookie(key, null, { path: '/'}); },

setStorage: function(key, value, options) { // If the browser doesn't support storage, return null if (typeof(Storage) === 'undefined') return null;

if (typeof(options) === 'undefined') options = {}; // Set defaults if (! ("expires" in options)) options.expires = 7; // Convert expires option to seconds from the current time options.expires = (new Date.getTime / 1000) + (options.expires * 60 * 60 * 24); // Then set the localStorage, add the options var obj = {data: value, options: options}; localStorage.setItem(key, JSON.stringify(obj)); }, getStorage: function(key) { // If the browser doesn't support storage, return null if (typeof(Storage) === 'undefined') return null;

var value = localStorage.getItem(key); // If the key doesn't exist, return null if (value === null) return null; value = JSON.parse(value); // If the value is expired, return null if (value.options.expires < new Date.getTime / 1000) { return null; } else { return value.data; } },  removeStorage: function(key) { // If the browser doesn't support storage, return null if (typeof(Storage) === 'undefined') return null; localStorage.removeItem(key); },

};

/**** Styles ****/ var s_wpFont = 'Verdana, "Verdana Ref", Corbel, "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", "DejaVu Sans", "Bitstream Vera Sans", "Liberation Sans", sans-serif'; var s_vteNavLink = { "margin": "5px 0px 0px 10px", "cursor": "pointer", "color": "#0B0B61" }; var s_vteWindow = { "position": "fixed", "width": "80%", "height": "80%", "background-color": "#FFFFFF", "top": "50px", "left": "10%", "box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-moz-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-webkit-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "z-index": "5" }; var s_vteSummaryInstructions = { "font-family": s_wpFont, "font-size": "11px", "font-style": "italic", "padding": "10px", "text-align": "center", }; var s_vteActiveProject = { "font-family": s_wpFont, "font-size": "10px", "border": "1px solid #000", "border-radius": "10px", "-moz-border-radius": "10px", "width": "30%", "float": "left", "background-color": "#EEE", "margin": "4px 5px", "padding": "3px 5px", "cursor": "pointer", }; var s_vteActiveProjectTitle = { "font-size": "11px", "font-weight": "bold", "height": "28px", }; var s_vteActiveProjectLabel = { "padding-left": "20px" }; var s_vteActiveProjectValue = { "font-style": "italic", "padding-left": "10px", "color": "#424242", }; var s_vteCloseProject = { "font-family": s_wpFont, "font-size": "10px", "color": "#424242", "float": "right", "padding": "1px 5px", "margin": "-26px 2px 0px 0px", }; var s_vteSortSummaryDiv = { "float": "left", "font-family": s_wpFont, "font-size": "10px", "margin": "4px 5px", "padding": "3px 5px", "width": "20%", "text-align": "center", "font-color": "#424242", }; var s_vteMemberImport = s_vteTaskEdit = s_vteMembersContribution = { "font-family": s_wpFont, "font-size": "12px", "position": "absolute", "top": "5%", "left": "5%", "width": "80%", "height": "80%", "background-color": "rgba(255,255,255,.98)", "padding": "20px", "box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-moz-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-webkit-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "border-radius": "10px", "-moz-border-radius": "10px", "overflow-y": "auto", }; var s_vteWindowRightContentTitle = { "font-family": s_wpFont, "font-size": "13px", "padding": "10px 10px 3px 10px", "margin": "0px 5px", "color": "#0B0B61", "float": "right", "font-weight": "bold", "font-style": "italic", "border": "1px solid #5882FA", // was #eee (light gray), now blue "border-top-left-radius": "10px", "border-top-right-radius": "10px", "-moz-border-top-left-radius": "10px", "-moz-border-top-right-radius": "10px", "cursor": "pointer", }; var s_vteMembersActionsMessage = { "font-family": s_wpFont, "position": "absolute", "top": "100px", "left": "25%", "background-color": "rgba(255,255,255,.95)", "padding": "20px", "box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-moz-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", "-webkit-box-shadow": "0 1px 6px rgba(0, 0, 0, 0.2)", }; var s_vteMembersActionsDiv = { "position": "absolute", "background-color": "#F5DA81", // yellow-orange "padding": "10px", "border-radius": "10px", "-moz-border-radius": "10px", }; var s_vteMembersActionsAction = { "font-family": s_wpFont, "font-size": "10px", "color": "#424242", "text-align": "left", "cursor": "pointer", "margin": "3px 0px 3px 5px", }; var s_vteWindowRightContentTasksAdd = s_vteWindowRightContentMembersAdd = { "font-family": s_wpFont, "font-size": "12px", "padding": "10px 0px 20px 0px", "color": "#424242", }; var s_vteMembersCreate = { "font-family": "'Trebuchet MS', Helvetica, sans-serif", "font-size": "16px", "margin": "15px 0px 15px 15px", "color": "#424242", "cursor": "pointer", "float": "left", }; var s_vteMembersEmpty = s_vteTasksEmpty = { "font-family": s_wpFont, "font-size": "12px", "padding": "0px 50px", "color": "#424242", };

// Communication view styles var s_vteCommunicationChatSend = { "font-family": s_wpFont, "font-size": "10px", "padding": "3px", "float": "right", }; var s_vteCommunicationChat = { "bottom": "5px", "position": "absolute", }; var s_vteCommunicationChatInput = { "width": "-moz-calc(100% - 4px)",   // Firefox "width": "-webkit-calc(100% - 4px)", // Webkit "width": "-o-calc(100% - 4px)",     // Opera "width": "calc(100% - 4px)",        // Standard "margin": "0px 0px 2px 0px", "font-family": s_wpFont, "font-size": "10px", }; var s_vteCommunicationChatMessages = { "list-style-type": "none", "margin": "0", "padding": "0", "overflow-y": "auto", }; var s_vteCommunicationChatLine = { "padding": "0px 1px", "list-style": "none", "font-family": s_wpFont, "font-size": "10px", }; var s_vteCommunicationChatUser = { "color": "#424242", "display": "inline", }; var s_vteCommunicationChatMessage = { "display": "inline", };

// Task view styles var s_vteTasksCreate = { "font-family": "'Trebuchet MS', Helvetica, sans-serif", "font-size": "16px", "margin": "15px 0px 15px 15px", "color": "#424242", "cursor": "pointer", "float": "left", }; var s_vteMembersView = s_vteTasksView = { "font-family": "'Trebuchet MS', Helvetica, sans-serif", "font-size": "13px", "color": "#424242", "cursor": "pointer", "display": "inline", "margin": "18px 0px 5px 30px", "float": "left", }; var s_vteDropdownList = { "position": "absolute", "background-color": "#EEE", "font-family": "'Trebuchet MS', Helvetica, sans-serif", "min-width": "85px", "border-bottom-left-radius": "5px", "border-bottom-right-radius": "5px", "-moz-border-bottom-left-radius": "5px", "-moz-border-bottom-right-radius": "5px",

}; var s_vteDropdownItem = { "padding": "2px 4px", }; var s_vteMembersTable = s_vteTasksTable = { "font-family": s_wpFont, "font-size": "12px", "color": "#424242", "border-collapse": "collapse", "width": "100%", }; var s_vteMembersRow = s_vteTasksRow = { "border-bottom": "1px solid #EEE", "cursor": "pointer", "padding": "3px 0px", }; var s_vteTasksTableTitle = { "width": "40%", }; var s_vteTasksTablePriority = s_vteTasksTableCreated = s_vteTasksTableDue = s_vteTasksTableOwner = { "text-align": "center", "min-width": "50px", }; var s_vteTasksTableComments = { "text-align": "center", "background-color": "#C9C9C9", }; var s_vteTaskCompleted = { "text-decoration": "line-through", }; // End Task view styles

// Task edit styles var s_vteTaskMarkComplete = { "float": "right", "margin": "0px" }; var s_vteTaskSave = { "float": "right", "margin": "0px 10px", }; var s_vteTaskClose = { "float": "right", "padding": "1px", "cursor": "pointer", };

var s_vteTaskEditLabel = { "display": "inline", "font-weight": "bold", }; var s_vteTaskEditInput = { "display": "inline", }; var s_vteTaskEditLeft = { "width": "45%", "float": "left", "margin": "10px 0px 10px 20px", }; var s_vteTaskEditRight = { "width": "45%", "float": "right", "margin": "10px 0px 10px 0px", }; var s_vteTaskEditOwners = { "color": "#424242", "margin-bottom": "20px", }; var s_vteOwnerRow = s_vteSubtaskRow = { "padding": "5px 0px", }; var s_vteTaskEditOwner = { "display": "inline", }; var s_vteTaskEditRemoveOwner = { "display": "inline", "padding": "0px 5px", "margin": "2px 5px 0px 5px", }; var s_vteTaskEditAddOwner = { "display": "inline", }; var s_vteTaskEditSubtasks = { "color": "#424242", }; var s_vteTaskEditSubtask = { "display": "inline", }; var s_vteTaskEditAddSubtask = { "display": "inline", }; var s_vteTaskEditSubcomplete = { "display": "inline", }; var s_vteTaskEditGraph = { "float": "right", "height": "100px", "width": "100%", "border": "1px solid #000", "margin-bottom": "20px", }; var s_vteTaskEditNotes = { }; // End Task edit styles

var s_vteMembersName = { "display": "inline", "font-family": s_wpFont, "font-size": "12px", "color": "#424242", "float": "left", "width": "20%", "padding-top": "5px", "border-bottom": "1px solid #EEE", }; var s_vteMembersDate = { "display": "inline", "font-family": s_wpFont, "font-size": "12px", "color": "#424242", "float": "left", "width": "20%", "padding-top": "5px", "border-bottom": "1px solid #EEE", }; var s_vteMembersComment = { "display": "inline", "font-family": s_wpFont, "font-size": "12px", "color": "#424242", "float": "left", "width": "-moz-calc(60% - 90px)",   // Firefox "width": "-webkit-calc(60% - 90px)", // Webkit "width": "-o-calc(60% - 90px)",     // Opera "width": "calc(60% - 90px)",        // Standard "text-align": "center", "padding-top": "5px", "border-bottom": "1px solid #EEE", }; var s_vteMemberImportLoading = { "font-family": s_wpFont, "font-size": "11px", "padding": "5px 0px 0px 10px", "color": "#424242", }; var s_vteTasksAction = s_vteMembersAction = { "display": "inline", "font-family": s_wpFont, "font-size": "12px", "color": "#424242", "float": "left", "width": "80px", "text-align": "center", "cursor": "pointer", "padding-top": "5px", "border-bottom": "1px solid #EEE", }; var s_vteTasksComplete = s_vteMembersInactive = { "color": "#A4A4A4", }; var s_vteWindowLeft = { "font-family": s_wpFont, "font-size": "10px", "float": "left", "padding": "5px 5px 5px 5px", "border-right": "1px solid #EEE", // Set width as 20% minus padding, borders, etc "width": "-moz-calc(20% - 11px)",   // Firefox "width": "-webkit-calc(20% - 11px)", // Webkit "width": "-o-calc(20% - 11px)",     // Opera "width": "calc(20% - 11px)",        // Standard // Same as width, height is 100% minus border, padding, etc "height": "-moz-calc(100% - 11px)", "height": "-webkit-calc(100% - 11px)", "height": "-o-calc(100% - 11px)", "height": "calc(100% - 11px)", }; var s_vteWindowRight = { "float": "right", "padding": "5px 5px 5px 5px", // Set width as 80% minus padding, borders, etc "width": "79%", "width": "-moz-calc(80% - 10px)",   // Firefox "width": "-webkit-calc(80% - 10px)", // Webkit "width": "-o-calc(80% - 10px)",     // Opera "width": "calc(80% - 10px)",        // Standard // Same as width, height is 100% minus border, padding, etc "height": "-moz-calc(100% - 21px)", "height": "-webkit-calc(100% - 21px)", "height": "-o-calc(100% - 21px)", "height": "calc(100% - 21px)", }; var s_vteWindowLeftOnline = { "float": "left", "width": "100%", "height": "15%", "background-color": "#F9F9F9" }; var s_vteWindowLeftChat = { "float": "left", "width": "100%", "height": "80%", "background-color": "#FFFFFF", "font-family": s_wpFont, "font-size": "12px", "padding": "10px 0px", }; var s_vteWindowRightTitle = { "float": "left", "width": "100%", "min-height": "10px", "background-color": "#F9F9F9", "border-bottom": "1px solid #EEE" }; var s_vteWindowRightTool = { "width": "100%", /* "background-color": "#F9F9F9", "border-bottom": "1px solid #000" }; var s_vteWindowLoading = { "width": "100%", "float": "left", "text-align": "center", "font-family": s_wpFont, "font-size": "10px", "color": "#6E6E6E", "margin-top": "10%", }; var s_vteWindowRightNav = { "width": "-moz-calc(100% - 20px)",   // Firefox "width": "-webkit-calc(100% - 20px)", // Webkit "width": "-o-calc(100% - 20px)",     // Opera "width": "calc(100% - 20px)",        // Standard "border-bottom": "1px solid #EEE", "margin": "0px 10px", }; var s_vteWindowRightContent = { "float": "left", "width": "100%", "height": "88%", "background-color": "#FFFFFF", "overflow-y": "auto", }; var s_vteWindowRightContentSummary = { "font-family": s_wpFont, "font-size": "13px", }; var s_vteWindowRightContentSummaryGraph = { "font-family": s_wpFont, "margin": "8px", "border": "1px solid #EEEEEE", "height": "112px", }; var s_vteWindowRightContentSummaryPages = { "font-family": s_wpFont, "margin": "8px", "border": "1px solid #EEEEEE", }; var s_vteWindowRightContentSummaryPage = { "height": "50px", }; var s_vteWindowRightContentSummaryNew = { "font-family": s_wpFont, "margin": "8px", "border": "1px solid #EEEEEE", }; var s_vteSummaryPageTitle = { "font-family": s_wpFont, "color": "#0B0080", "font-size": "10px", "font-style": "italic", "border-bottom": "solid 1px #EEE", "padding-top": "10px", "cursor": "pointer", }; var s_vteMembersContributionEdits = { "font-family": s_wpFont, "font-size": "10px", "margin": "8px", "border": "1px solid #EEEEEE", "height": "250px", }; var s_vteLoadingText = { "font-family": s_wpFont, "font-size": "10px", "margin-top": "20px", "color": "#848484", "-webkit-animation-name": "glow", "-webkit-animation-duration": "1s", "-webkit-animation-iteration-count": "infinite", "-webkit-animation-direction": "alternate", "-webkit-animation-timing-function": "ease-in-out", "-moz-animation-name": "glow", "-moz-animation-duration": "1s", "-moz-animation-iteration-count": "infinite", "-moz-animation-direction": "alternate", "-moz-animation-timing-function": "ease-in-out", "-o-animation-name": "glow", "-o-animation-duration": "1s", "-o-animation-iteration-count": "infinite", "-o-animation-direction": "alternate", "-o-animation-timing-function": "ease-in-out", "animation-name": "glow", "animation-duration": "1s", "animation-iteration-count": "infinite", "animation-direction": "alternate", "animation-timing-function": "ease-in-out" }; var s_vteTitle = { "font-family": s_wpFont, "font-size": "20px", "font-weight": "normal", "float": "left", "padding": "5px 0px 5px 10px" }; var s_vteTitleActions = { "float": "right", "padding": "0px 10px 0px 0px", }; var s_vteTitleAction = { "float": "left", "font-family": s_wpFont, "font-size": "12px", "cursor": "pointer", "padding": "5px 0px 0px 5px", }; var s_vteProjectSelectLabel = { "font-family": s_wpFont, "font-size": "12px", "padding": "5px 0px 0px 7px", }; var s_vteProjectSelectInput = { "margin": "5px 0px", "height": "1.4em", "background-color": "transparent", "color": "#000", "outline": "none", "font-family": s_wpFont, "font-size": "12px", "padding": "2px 5px", "width": "-moz-calc(100% - 12px)",   // Firefox "width": "-webkit-calc(100% - 12px)", // Webkit "width": "-o-calc(100% - 12px)",     // Opera "width": "calc(100% - 12px)",        // Standard }; var s_vteProjectSelectMulti = { "position": "absolute", "width": "50%", "max-height": "20%", "overflow-y": "auto", "margin-top": "-4px", "padding": "7px 10px", "background-color": "#EEE", "border-bottom-left-radius": "10px", "border-bottom-right-radius": "10px", "-moz-border-bottom-left-radius": "10px", "-moz-border-bottom-right-radius": "10px", "border": "1px solid #000", "z-index": "2", }; var s_vteProjectSelectMultiProj = { "font-family": s_wpFont, "font-size": "12px", "cursor": "pointer", "padding-left": "1em", "text-indent": "-1em", "padding-bottom": "4px", };

// Only load vte on the Wikipedia namespace (may limit to project pages later) window.onload = function { //if (mw.config.get('wgNamespaceNumber') === '4') { console.log("Loading VTE"); mw.loader.using( ["mediawiki.api"], function {     $(vte.initialize);      // And create the websocket      var t = setInterval(function { if (typeof(io) !== 'undefined') { clearInterval(t); vte_sock = io(data_api, {secure: true}); // Emit vte initialize vte_sock.emit("vte_init", {           name: mw.config.get("wgUserName"),            time: new Date,            page: mw.config.get("wgTitle"),            namespace: mw.config.get("wgNamespaceNumber"),          }); // And handle updates to who's online vte_sock.on("online", function(obj) {           if ($("#vte-window-left-online-num").length > 0) {              $("#vte-window-left-online-num").html(Object.keys(obj).length);              }          }); }      }, 100);    });  //} } //