//<nowiki> - this prevents double left braces being misinterpreted by the MediaWiki parser
// global variables, as required
var vte_sock = true;
var action =
"<svg height='10' width='10'>" +
" <polygon points='2,3 8,3 5,8' style='fill:black;stroke:black;stroke-width:1' />" +
" Sorry, your browser does not support inline SVG." +
"</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 = $(
"<div class='vectorMenu' id='p-vte'>" +
" <h3><span>VTE</span></h3>" +
"</div>"
).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 = $(
"<div id='vte-window'>" +
" <div id='vte-window-left'>" +
" <div id='vte-window-left-online'>" +
" Online users: <span id='vte-window-left-online-num'></span>" +
" </div>" +
" <div id='vte-window-left-chat' />" +
" </div>" +
" <div id='vte-window-right'> " +
" <div id='vte-window-right-title' />" +
" <div id='vte-window-right-tool' />" +
" <div id='vte-window-right-nav' />" +
" <div id='vte-window-right-content'>" +
" <div class='vte-page' id='vte-window-loading'/>" +
" <div class='vte-page' id='vte-window-explorer'/>" +
" <div class='vte-page' id='vte-window-summary'/>" +
" <div class='vte-page' id='vte-window-members'/>" +
" <div class='vte-page' id='vte-window-tasks'/>" +
" <div class='vte-page' id='vte-window-communication'/>" +
" </div>" +
" </div>" +
" <div style='clear: both;'></div>" +
"</div>"
);
// 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(
"<div>" +
"Loading..." +
"</div>"
);
$("#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 <topEditors> 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 <topPages> 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 <topPages> 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 =
"<!-- To add a task, copy the following line and place inside the ListMaster invocation: \n" +
" {{#title=<task title>|description=<task description>|priority=<task priority>}}\n" +
"-->\n\n" +
"{{#invoke:ListMaster|printTable|style=table|display=title,description,priority|\n" +
"}}";
} else {
// Otherwise, build the task string from the tasks object
tasks_str = vte_project.tasks.pre +
"{{#invoke:ListMaster|printTable|style=" + vte_project.tasks.style +
"|display=" + vte_project.tasks.display + "|\n";
for (var i in vte_project.tasks.struc) {
tasks_str += " {{#";
var attribs = [];
for (var n in vte_project.tasks.struc[i]) {
// Don't save empty values
if (vte_project.tasks.struc[i][n]) attribs.push(n + "=" + vte_project.tasks.struc[i][n]);
}
tasks_str += attribs.join("|") + "}}\n";
}
tasks_str += "}}" + 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 =
"<!-- To add a member, copy the following line and place inside the ListMaster invocation: \n" +
" {{#name=<user name>|interests=<what you're interested in>}}\n" +
"-->\n\n" +
"{{#invoke:ListMaster|printTable|style=table|display=name,interests|\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 = $(
"<div id='vte-title'>Virtual Team Explorer</div>" +
"<div id='vte-title-actions'>" +
" <div id='vte-title-action-user' title='View user information'>" +
" <img src='https://upload.wikimedia.org/wikipedia/commons/0/0a/Gnome-stock_person.svg' width='15' height='15' style='padding: 5px;'/>" +
" </div>" +
" <div id='vte-title-action-settings' title='View VTE settings'>" +
" <img src='https://upload.wikimedia.org/wikipedia/commons/7/77/Gear_icon.svg' width='25' height='25'/>" +
" </div>" +
" <div id='vte-title-action-close' title='Close the VTE'>" +
" <img src='https://upload.wikimedia.org/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'/>" +
" </div>" +
"</div>"
);
// 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 = $(
"<input id='vte-project-select-input' type='text' placeholder='Enter a WikiProject to explore'/>" +
"<div id='vte-project-select-multi' />"
);
// 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(
"<div style='display: block;' class='vte-project-select-multi-proj' " +
" vte-p-id='" + projects[i]['p_id'] + "' " +
" vte-p-title='" + projects[i]['p_title'] + "' vte-p-seen='0' " +
" vte-p-touched='0' vte-p-created='" + projects[i]['p_created'] + "' >" +
projects[i]['p_title'].replace(/_/g, " ") +
"</div>"
);
});
// 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(
"<div class='vte-project-select-multi-none' style='font-size: 10px; color: #848484;'>" +
" No matching projects found" +
"</div>"
);
} else {
$(".vte-project-select-multi-none").remove();
}
});
},
populateProjectExplorer: function() {
// Clear the content div, print initial greeting
$("#vte-window-explorer").html(
"<div id='vte-summary-instructions'>" +
" Search for a WikiProject in the box above, or select from the list of most " +
" active WikiProjects below to continue.<br/>" +
" (Projects below represent the most active WikiProjects by edits to " +
" member pages within the last month, limited to those with at least 30 edits)" +
"</div>" +
"<div id='vte-summary-projects' />"
);
$("#vte-summary-instructions").css(s_vteSummaryInstructions);
// Add in buttons to sort projects by edits, pages edited, or edits per page
$("#vte-summary-projects").append(
"<div class='vte-sort-summary-div'>" +
" <input type='submit' id='vte-sort-summary-by-edits' class='vte-sort-summary' vte-sort-summary-by='edits' value='Sort by edits' />" +
"</div>" +
"<div class='vte-sort-summary-div'>" +
" <input type='submit' class='vte-sort-summary' vte-sort-summary-by='pages' value='Sort by pages' />" +
"</div>" +
"<div class='vte-sort-summary-div'>" +
" <input type='submit' class='vte-sort-summary' vte-sort-summary-by='ratio' value='Sort by ratio' />" +
"</div>" +
"<div class='vte-sort-summary-div'>" +
" <input type='submit' class='vte-sort-summary' vte-sort-summary-by='project_edits' value='Sort by project edits' />" +
"</div>"
);
// 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(
"<div id='vte-active-project-" + proj.p_id + "' class='vte-active-project' p_id='" + proj.p_id + "' " +
" p_title='" + proj.p_title + "' p_created='" + proj.p_created + "' " + style + ">" +
" <table style='width: 100%;'><tr>" +
" <td colspan='2' class='vte-active-project-title'>" + proj.p_title.replace(/_/g," ") + "</td></tr>" +
" <tr><td class='vte-active-project-label'>Project Edits</td>" +
" <td class='vte-active-project-value'>" + proj["4"] + "</td>" +
" </tr><tr><td class='vte-active-project-label'>Edits</td>" +
" <td class='vte-active-project-value'>" + proj.total_edits + "</td>" +
" </tr><tr><td class='vte-active-project-label'>Pages Edited</td>" +
" <td class='vte-active-project-value'>" + proj.total_pages + "</td>" +
" </tr><tr><td class='vte-active-project-label'>Edits per page</td>" +
" <td class='vte-active-project-value'>" + (Math.round(proj.ratio * 100) / 100) + "</td>" +
" </tr></table>" +
"</div>"
);
// 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("<input type='submit' class='vte-close-project' value='X'/>");
$(".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(
"<div id='vte-window-right-content-summary'>" +
" <div id='vte-window-right-content-summary-title' />" +
" <div id='vte-window-right-content-summary-p-edits'>" +
" Edits to Project (blue) and Project Talk (grey) pages" +
" <div style='height: 10px; width: 100%; border-bottom: 1px solid #000;'></div>" +
" <div id='vte-loading-edits' class='vte-loading'>Loading project edit data...</div>" +
" <div id='vte-project-summary-graph' style='height:80px' />" +
" </div>" +
" <div id='vte-window-right-content-summary-pages'>" +
" Most active articles in the last 30 days (showing the last year)" +
" <div style='height: 10px; width: 100%; border-bottom: 1px solid #000;'></div>" +
" <div id='vte-loading-pages' class='vte-loading'>Loading revision history for project pages...</div>" +
" </div>" +
"</div>"
);
$("#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(
"<div class='vte-summary-page-title'>" + pages[id][0].tp_title.replace(/_/g," ") + "</div>" +
"<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(
"<div class='vte-content-nav' id='vte-communication'>Communication</div>" +
"<div class='vte-content-nav' id='vte-tasks'>Tasks</div>" +
"<div class='vte-content-nav' id='vte-members'>Members</div>" +
"<div class='vte-content-nav' id='vte-summary'>Summary</div>" +
"<div class='vte-content-nav-spacer' style='clear: both;'/>"
);
// 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(
"<div class='vte-members-create'>+ Add member</div>" +
"<div class='vte-members-import'>+ Import members from project network</div>" +
"<div id='vte-members-view'>View:All" + action + "</div>" +
"<div id='vte-members-sort'>Sort:Created" + action + "</div>" +
"<div style='clear: both;'></div>"
);
// 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(
"<div class='vte-members-empty'>" +
" 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." +
"</div>"
);
// 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(
"<table class='vte-members-table' cellpadding='0'>" +
" <colgroup>" +
" <col class='vte-members-table-name' />" +
" <col class='vte-members-table-since' />" +
" <col class='vte-members-table-proj' />" +
" <col class='vte-members-table-page' />" +
" </colgroup>" +
" <tbody class='vte-members-table-body'/>" +
"</table>"
);
// 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 + "</div>" +
" <td id='vte-members-table-since-" + i + "' class='vte-members-table-since vte-m-td'>" + since + "</div>" +
" <td id='vte-members-table-proj-" + i + "' class='vte-members-table-proj vte-m-td'>" + proj + "</div>" +
" <td id='vte-members-table-page-" + i + "' class='vte-members-table-page vte-m-td'>" + page + "</div>" +
"</tr>"
);
}
// 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(
"<div id='vte-members-view-actions'>" +
" <div id='vte-members-view-all' class='vte-dropdown-item'>All</div>" +
" <div id='vte-members-view-activity' class='vte-dropdown-item'>Activity</div>" +
" <div id='vte-members-view-explicit' class='vte-dropdown-item'>Explicit</div>" +
"</div>"
);
$("#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(
"<div id='vte-members-sort-actions'>" +
" <div id='vte-members-sort-name' class='vte-dropdown-item'>Name</div>" +
" <div id='vte-members-sort-since' class='vte-dropdown-item'>Member Since</div>" +
" <div id='vte-members-sort-proj' class='vte-dropdown-item'>Project Edits</div>" +
" <div id='vte-members-sort-page' class='vte-dropdown-item'>Page Edits</div>" +
"</div>"
);
$("#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(
"<div id='vte-member-import'>" +
" <div id='vte-import-close'>" +
" <img src='https://upload.wikimedia.org/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'>" +
" </div>" +
" <input type='submit class='vte-import-save' value='Import' />" +
" <div style='margin-top: 10px; clear: both;'> </div>" +
" <div id='vte-import-loading-links' class='vte-loading'>Loading user links on project pages...</div>" +
" <div id='vte-import-loading-proj' class='vte-loading'>Loading edits to project pages...</div>" +
" <div id='vte-import-loading-page' class='vte-loading'>Loading project pages...</div>" +
" <div id='vte-import-loading-edits' class='vte-loading'>Loading edits by top project editors...</div>" +
" <table style=''>" +
" <colgroup>" +
" <col class='vte-import-check'/>" +
" <col class='vte-import-name'/>" +
" <col class='vte-import-proj'/>" +
" <col class='vte-import-page'/>" +
" <col class='vte-import-user-link'/>" +
" </colgroup>" +
" <tbody class='vte-import-table-body'/>" +
" </table>" +
"</div>"
);
// 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(
"<div class='vte-tasks-create'>+ Add task</div>" +
"<div id='vte-tasks-view'>View:All" + action + "</div>" +
"<div id='vte-tasks-sort'>Sort:Created" + action + "</div>" +
"<div style='clear: both;'></div>"
);
$("#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(
"<div class='vte-tasks-empty'>" +
" This project currently does not have any tasks listed. " +
" Add tasks by clicking the '+ Add task' link." +
"</div>"
);
// 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(
"<table class='vte-tasks-table' cellpadding='0'>" +
" <colgroup>" +
" <col class='vte-tasks-table-created'>" +
" <col class='vte-tasks-table-priority'>" +
" <col class='vte-tasks-table-title'>" +
" <col class='vte-tasks-table-comments'>" +
" <col class='vte-tasks-table-owner'>" +
" </colgroup>" +
" <tbody class='vte-tasks-table-body'/>" +
"</table>"
);
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 <p> 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>" +
" <td id='vte-tasks-table-priority-" + i + "' class='vte-tasks-table-priority vte-t-td'>" + p + "</td>" +
" <td id='vte-tasks-table-title-" + i + "' class='vte-tasks-table-title vte-t-td'>" + t + "</td>" +
" <td id='vte-tasks-table-comments-" + i + "' class='vte-tasks-table-comments vte-t-td'>" + com + " comments</td>" +
" <td id='vte-tasks-table-owner-" + i + "' class='vte-tasks-table-owner vte-t-td'>" + o + "</td>" +
"</tr>"
);
}
// 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(
"<div id='vte-tasks-view-actions'>" +
" <div id='vte-tasks-view-all' class='vte-dropdown-item'>All</div>" +
" <div id='vte-tasks-view-open' class='vte-dropdown-item'>Open</div>" +
" <div id='vte-tasks-view-closed' class='vte-dropdown-item'>Closed</div>" +
"</div>"
);
$("#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(
"<div id='vte-tasks-sort-actions'>" +
" <div id='vte-tasks-sort-created' class='vte-dropdown-item'>Created</div>" +
" <div id='vte-tasks-sort-priority' class='vte-dropdown-item'>Priority</div>" +
" <div id='vte-tasks-sort-title' class='vte-dropdown-item'>Title</div>" +
" <div id='vte-tasks-sort-comments' class='vte-dropdown-item'>Comments</div>" +
" <div id='vte-tasks-sort-owner' class='vte-dropdown-item'>Owner</div>" +
"</div>"
);
$("#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(
"<div id='vte-communication-chat'>" +
" <ul id='vte-communication-chat-messages' />" +
" <div id='vte-communication-chat-form'>" +
" <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' />" +
" </form>" +
" </div>" +
"</div>"
);
// 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'>" +
" <div class='vte-communication-chat-user'>" + obj.name + ":</div>" +
" <div class='vte-communication-chat-message'>" + obj.message + "</div>" +
"</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(
"<div id='vte-task-edit'>" +
" <div id='vte-task-close'>" +
" <img src='https://upload.wikimedia.org/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'>" +
" </div>" +
" <input type='submit' class='vte-task-save' value='Save' />" +
complete_button +
" <div style='margin-top: 10px; clear: both;'> </div>" +
" <table style=''>" +
" <tr>" +
" <td> " +
" <div id='vte-task-title-label' class='vte-task-edit-label'>Task Title: </div>" +
" </td>" +
" <td>" +
" <div class='vte-task-edit-input'>" +
" <input type='text' id='vte-task-title' value='" + task.title + "'/>" +
" </div>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td>" +
" <div id='vte-task-page-label' class='vte-task-edit-label'>Related Page: </div>" +
" </td>" +
" <td>" +
" <div class='vte-task-edit-input'>" +
" <input type='text' id='vte-task-page' value='" + task.page + "'/>" +
" </div>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td colspan=2>" +
" <table><tr><td style='width:33%;'>" +
" <div id='vte-task-priority-label' class='vte-task-edit-label'>Priority: </div>" +
" <select id='vte-task-priority'>" +
" <option value='0' " + (task.priority == 0 ? "SELECTED" : "") + ">0 (most urgent)</option>" +
" <option value='1' " + (task.priority == 1 ? "SELECTED" : "") + ">1</option>" +
" <option value='2' " + (task.priority == 2 ? "SELECTED" : "") + ">2</option>" +
" <option value='3' " + (task.priority == 3 ? "SELECTED" : "") + ">3</option>" +
" <option value='4' " + (task.priority == 4 ? "SELECTED" : "") + ">4 (least urgent)</option>" +
" </select>" +
" </td><td style='width:33%;'>" +
" <div id='vte-task-remaining-label' class='vte-task-edit-label'>Time Remaining: </div>" +
" <div class='vte-task-edit-input'>" +
" <input type='text' id='vte-task-remaining' value='" + task.remaining + "'/>" +
" </div>" +
" </td><td style='width;33%;'>" +
" <div id='vte-task-due-label' class='vte-task-edit-label'>Due date (YYYY-mm-dd): </div>" +
" <div class='vte-task-edit-input'>" +
" <input type='text' id='vte-task-due' value='" + task.due + "'/>" +
" </div>" +
" </td></tr></table>" +
" </td>" +
" </tr>" +
" </table>" +
" <div style='clear: both;'> </div>" +
" <div class='vte-task-edit-left'>" +
" <div class='vte-task-edit-label' style='display: block;'>Assigned To:</div>" +
" <div class='vte-task-edit-owners' />" +
" <div class='vte-task-edit-label' style='display: block; margin-top: 20px;'>Sub Tasks:</div>" +
" <div class='vte-task-edit-subtasks' />" +
" </div>" +
" <div class='vte-task-edit-right'>" +
" <div class='vte-task-edit-graph' />" +
" <div class='vte-task-edit-label' style='display: block;'>Comments/Details</div>" +
" <div class='vte-task-edit-notes'>" +
" <textarea id='vte-task-notes' rows='5' cols='40'>" + task.notes + "</textarea>" +
" </div>" +
" </div>" +
"</div>"
);
// 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(
"<div class='vte-owner-row'>" +
" <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] + "</div>" +
"</div>"
);
}
// And then add the owner's edit field
$(".vte-task-edit-owners").append(
"<div class='vte-task-edit-input' id='vte-task-edit-owner-input'>" +
" <input type='text' id='vte-task-owner' value='' />" +
"</div>" +
"<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(
"<div class='vte-subtask-row'>" +
" <input type='checkbox' index='" + i + "' class='vte-task-edit-subcomplete' />" +
" <div class='vte-task-edit-subtask' index='" + i + "'>" + task["subtask" + i] + "</div>" +
"</div>"
);
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(
"<div class='vte-task-edit-input' id='vte-task-edit-subtask-input'>" +
" <input type='text' id='vte-task-subtask' value='' />" +
"</div>" +
"<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() +
" </div>"+
"</div>"
);
$("#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() + "</div>" +
"</div>"
);
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);
});
//}
}
//</nowiki>