User:Md gilbert/vte.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Md gilbert/vte. |
//<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>