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 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 = 'https://alahele.ischool.uw.edu:8997/js/d3.min.js';
head.appendChild(script);
script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://alahele.ischool.uw.edu:8997/js/socket.io-1.2.0.js';
head.appendChild(script);
// 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.renderOverlay();
});
}, // 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-content' />" +
" </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-content").css(s_vteWindowRightContent);
// Fill in vte elements
vte.populateTitle();
vte.populateTool();
vte.populateProject();
vte.populateNav();
vte.populateContent();
vte.drawChat();
},
// 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='/media/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='/media/wikipedia/commons/7/77/Gear_icon.svg' width='25' height='25'/>" +
" </div>" +
" <div id='vte-title-action-close' title='Close the VTE'>" +
" <img src='/media/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-window").hide();
});
},
// populateTool - draws tool bar content
populateTool: function(tool) {
},
// populateProject - draws the project browser content
populateProject: function() {
// Fetch all projects or load from previous request
// TODO: allow cookies before deploying
var projects = []; // $.cookie( 'vte-projects' );
if (projects.length > 0) {
vte.drawProjectSelect(projects);
} else {
// Add search box and main nav
var $projectSelect = $(
"<input id='vte-project-select-input' type='text' placeholder='Loading WikiProject data...'/>" +
"<div id='vte-project-select-multi' />"
);
// Add it
$("#vte-window-right-tool").html($projectSelect);
// Style it
$("#vte-project-select-label").css(s_vteProjectSelectLabel);
$("#vte-project-select-input").css(s_vteProjectSelectInput);
// Make the request
//var url = 'http://tools.wmflabs.org/catscan2/catscan2.php?depth=10&categories=Active_WikiProjects&ns%5B4%5D=1&doit=1&format=json';
//var url = 'http://tools.wmflabs.org/catscan2/catscan2.php?depth=2&categories=Active_WikiProjects&ns%5B4%5D=1&topcats_no_talk=1&format=json&doit=1';
var url = 'https://alahele.ischool.uw.edu:8997/api/getProjects';
$.ajax({
url: url,
dataType: "json",
success: function(data, stat, xhr) {
//var projects = data["*"][0]["*"];
var projects = data["result"];
// TODO: Allow cookies before deploying
/*
$.cookie('vte-projects', projects, {
expires: 7, // expire in 7 days
path: '/' // domain-wide, entire wiki
});
*/
vte.drawProjectSelect(projects);
},
error: function(xhr, stat, err) {
console.error("Failed to request project data from API: " + JSON.stringify(xhr));
$("#vte-window-left-project").html("Failed to request project data from API: " + JSON.stringify(xhr));
},
complete: function() {
$("#vte-project-loading").remove();
},
});
}
},
// drawProjectSelect - called by populateProject after successfully requesting project data.
// Draws project select box
drawProjectSelect: function(projects) {
// Remove loading placeholder
$("#vte-project-select-input").attr("placeholder", "Enter a WikiProject to explore");
// Add the actions (everytime there's a key-up, update list of visible projects)
$("#vte-project-select-input").on("keyup", function() {
// FIRST, update the list of active projects
$(".vte-active-project").each(function(i, v) {
var project = $(v).attr("p_title").replace(/ /g, "_").toLowerCase();
var input = $("#vte-project-select-input").val().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
// Empty the project selection div
$("#vte-project-select-multi").html("");
// Then for each project add to the dropdown if it matches the input text
$.each(projects, function(i,v) {
// Make sure the div exists
if ($("#vte-project-select-multi").length === 0) {
$("#vte-project-select").after("<div id='vte-project-select-multi' />");
}
$("#vte-project-select-multi").show();
var project = projects[i]['p_title'].replace(/_/g, " ").toLowerCase();
var input = $("#vte-project-select-input").val().replace(/_/, " ").toLowerCase();
if (project.indexOf(input) != -1) {
$("#vte-project-select-multi").append(
"<div 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>"
);
}
});
if ($("#vte-project-select-multi").html() == "") {
$("#vte-project-select-multi").append(
"<div class='vte-project-select-multi-proj' style='color: #848484;'>No matching projects found</div>"
);
}
// Style the container and projects
$("#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 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();
$("#vte-project-select-input").val($(evt.currentTarget).html());
// Load the project summary
console.log("loading summary for project " + title + ", id: " + id);
$("#vte-window").data("vte-project", title);
$("#vte-window").data("vte-project-attr", {
title: title,
id: id,
created: created,
});
vte.populateNav();
vte.drawProjectSummary();
});
});
},
// drawProjectSummary - draws summary information for the project once it is selected
// from the vte-project-select-multi dropdown (or clicked on)
drawProjectSummary: function() {
var title, id, created;
title = $("#vte-window").data("vte-project-attr").title;
id = $("#vte-window").data("vte-project-attr").id;
created = $("#vte-window").data("vte-project-attr").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"),
});
// 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-window").data("vte-project-attr", 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.populateContent();
vte.populateNav();
});
// Clear any existing data in the content window and add summary divs
$("#vte-window-right-content").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);
// Request summary data from our backend
var t = title.replace(/ /g, "_");
var sd = created.substr(0, 8);
var sw = vte.convertDateToWikiWeek(sd);
var url = "https://alahele.ischool.uw.edu:8997/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 = "https://alahele.ischool.uw.edu:8997/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();
url = "https://alahele.ischool.uw.edu:8997/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.drawProjectEdits({result: pages[id]}, sw, "vte-window-right-content-summary-page-" + id);
}
$(".vte-summary-page-title").css(s_vteSummaryPageTitle);
},
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));
},
});
},
// drawProjectPages - draws a summary of pages in a project
drawProjectPages: function(data) {
// Structure the pages for vis
var pages = Array(16);
for (var i = 0; i < pages.length; i++) pages[i] = { "namespace": i, "count": 0 };
for (var p in data["result"]) {
for (var i in data["result"][p]) {
if (data["result"][p][i].tp_namespace > 15) continue;
pages[ data["result"][p][i].tp_namespace ].count += 1;
}
}
// D3 bar graph
var margin = {top: 10, right: 20, bottom: 30, left: 60},
w = $("#vte-window-right-content").width() - 30 - margin.right - margin.left,
h = 100 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, w], .1);
var y = d3.scale.linear()
.range([h, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(16, "");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(4, "");
var vis = d3.select("#vte-window-right-content-summary-pages")
.append("svg:svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
x.domain(pages.map(function(d) { return d.namespace; }));
y.domain([0, d3.max(pages, function(d) { return d.count; })]);
vis.append("g")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.append("text")
.attr("y", h-10)
.attr("x", w / 2)
.attr("dy", ".71em")
.style("text-anchor", "center")
.text("Namespace");
vis.append("g")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("# Pages");
vis.selectAll(".bar")
.data(pages)
.enter().append("rect")
.attr("x", function(d) { return x(d.namespace); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return h - y(d.count); });
},
// 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() - 1;
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
var w = $("#vte-window-right-content").width() - 30,
h = $("#" + div_id).height();
//h = 80;
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")) {
$("#vte-window-right-tool").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-content-nav").css("color", "#0B0B61");
$("#vte-summary").css("color", "#000");
vte.drawProjectSummary();
} else if (id == "vte-members") {
$(".vte-content-nav").css("color", "#0B0B61");
$("#vte-members").css("color", "#000");
vte.clickMembers();
} else if (id == "vte-tasks") {
$(".vte-content-nav").css("color", "#0B0B61");
$("#vte-tasks").css("color", "#000");
vte.clickTasks();
} else if (id == "vte-communication") {
$(".vte-content-nav").css("color", "#0B0B61");
$("#vte-communication").css("color", "#000");
vte.clickCommunication();
} else {
console.error("Unknown vte action: " + id);
}
});
} else {
$(".vte-content-nav").remove();
}
},
// Functions to populate the primary vte systems (ie, members, tasks, etc)
clickMembers: function() {
// 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"),
view: "Members"
});
console.log("vte - drawing member content");
// Load the Members page for the current project (should be Wikipedia:<project name>/Members
var project = $("#vte-window").data("vte-project");
$.getJSON(
mw.util.wikiScript('api'),
{
format: 'json',
action: 'query',
prop: 'revisions',
rvprop: 'content',
rvlimit: 1,
titles: 'Wikipedia:' + project + "/Members",
}
)
.done(function(data) {
var page, text;
try {
// This should only return one page...
for ( page in data.query.pages) {
text = data.query.pages[page].revisions[0]['*'];
}
} catch( e ) {
console.error("Failed to request members page for project " + project);
}
vte.drawMembers(text);
})
.fail(function() {
console.error("Failed to request members page for project " + project);
});
},
clickTasks: function() {
// 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"),
view: "Tasks"
});
console.log("vte - drawing task content");
// Load the Tasks page for the current project (should be Wikipedia:<project name>/Tasks
var project = $("#vte-window").data("vte-project");
$.getJSON(
mw.util.wikiScript('api'),
{
format: "json",
action: "query",
prop: "revisions",
rvprop: "content",
rvlimit: 1,
titles: "Wikipedia:" + project + "/Tasks",
}
)
.done(function(data) {
var page, text;
try {
// This should only return one page
for (page in data.query.pages) {
text = data.query.pages[page].revisions[0]['*'];
}
} catch(e) {
console.error("Failed to request tasks page for project " + project);
}
vte.drawTasks(text);
})
.fail(function() {
console.error("Failed to request tasks page for project " + project);
});
},
clickCommunication: function() {
// 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"),
view: "Communication"
});
console.log("vte - drawing communication content");
var project = $("#vte-window").data("vte-project");
// TODO: Load wiki communication
vte.drawCommunication();
},
drawMembers: function(data) {
// Clear the current content window
$("#vte-window-right-content").html("");
// Grab current project users from the Members page text
var obj = vte.parseTable(data, "Members", "printMembersTable");
// Projects have the option to include anything in the members table, but for
// the VTE we'll want to display the name, member since, and project roles.
// Possible column names given in [[Module:Members]] (although these aren't enforced).
var c_map = {};
for (var i in obj.cols) {
if (obj.cols[i].slice(0,4) == "name") { c_map["name"] = i; }
else if (obj.cols[i] == "member_since") { c_map["member_since"] = i; }
else if (obj.cols[i] == "project_roles") { c_map["project_roles"] = i; }
else if (obj.cols[i] == "member_status") { c_map["member_status"] = i; }
}
// Once we've parsed all the users from the Members page, draw the VTE members content
var roles_input = "project_roles" in c_map ?
" Project Role: <input type='text' id='vte-members-add-comment' />" : "";
$("#vte-window-right-content").append(
"<div id='vte-window-right-content-members-add'>" +
" <form id='vte-member-submit' action='javascript();'>" +
" Add Member: <input type='text' id='vte-members-add-name' />" +
roles_input +
" <input type='submit' value='Add' />" +
" </form>" +
"</div>"
);
$("#vte-window-right-content-members-add").css(s_vteWindowRightContentMembersAdd);
// Display existing users (header on top, only show columns that were saved on the project's Members page)
$("#vte-window-right-content").append(
"<div class='vte-members-actions' style='font-weight: bold; text-align: center;'>Actions</div>"
);
if ("name" in c_map) {
$("#vte-window-right-content").append(
"<div class='vte-members-name' style='font-weight: bold; text-align: center;'>User name</div>"
);
}
if ("member_since" in c_map) {
$("#vte-window-right-content").append(
"<div class='vte-members-date' style='font-weight: bold; text-align: center;'>Member since</div>"
);
}
if ("project_roles" in c_map) {
$("#vte-window-right-content").append(
"<div class='vte-members-comment' style='font-weight: bold; text-align: center;'>Project roles</div>"
);
}
$("#vte-window-right-content").append("<div style='clear: both;'/>");
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>";
for (var i in obj.struc) {
// Same as above, try to display name, member_since, and project_roles. Skip values that aren't there.
var n = "name" in c_map ? obj.struc[i][c_map["name"]] : "";
var d = "member_since" in c_map ? obj.struc[i][c_map["member_since"]] : "";
var r = "project_roles" in c_map ? obj.struc[i][c_map["project_roles"]] : "";
if (! n) n = " ";
if (! d) d = " ";
if (! r) r = " ";
if (d != " ") {
var m = d.match(/(\d+):(\d+), (\d+) (\S+) (\d+)/);
var date = m === null ? "" : new Date(m[5] + "-" + vte.getMonth(m[4]) + "-" + m[3] + " " + m[1] + ":" + m[2] + ":00 UTC");
if (date) d = vte.getDateStr(date);
}
// Inactive members will be grey
var inactive = "";
if ("member_status" in c_map && obj.struc[i][c_map["member_status"]] == "Inactive")
inactive = " vte-members-inactive ";
// Action icon is displayed first
$("#vte-window-right-content").append(
"<div class='vte-members-actions' vte-name='" + n + "' " + " id='action_" + n + "'>" + action + "</div>"
);
if ("name" in c_map) {
$("#vte-window-right-content").append(
"<div id='vte-members-name-" + n + "' class='vte-members-name " + inactive + "'>" + n + "</div>"
);
}
if ("member_since" in c_map) {
$("#vte-window-right-content").append(
"<div id='vte-members-date-" + n + "' class='vte-members-date " + inactive + "'>" + d + "</div>"
);
}
if ("project_roles" in c_map) {
$("#vte-window-right-content").append(
"<div id='vte-members-comment-" + n + "' class='vte-members-comment " + inactive + "'>" + r + "</div>"
);
}
$("#vte-window-right-content").append("<div style='clear: both;' />");
// Add action to open Actions menu on click
$("#action_" + n).click(vte.clickMemberActions);
}
$(".vte-members-name").css(s_vteMembersName);
$(".vte-members-date").css(s_vteMembersDate);
$(".vte-members-comment").css(s_vteMembersComment);
$(".vte-members-actions").css(s_vteMembersAction);
$(".vte-members-inactive").css(s_vteMembersInactive);
// Action to add new member
$("#vte-member-submit").submit(function(e) {
e.preventDefault();
console.log("Adding user to members list");
// TODO: Verify that the user actually exists, potentially?
var d = new Date();
var n = $("#vte-members-add-name").val();
var member = [];
if("name" in c_map) member[ c_map["name"] ] = $("#vte-members-add-name").val();
if("project_roles" in c_map) member[ c_map["project_roles"] ] = $("#vte-members-add-comment").val();
if("member_since" in c_map) member[ c_map["member_since"] ] = vte.getWikiDateStr(d);
if("member_status" in c_map) member[ c_map["member_status"] ] = "Active";
var obj = $("#vte-window").data("Members");
obj.struc.push(member)
// And update the local members object
$("#vte-window").data("Members", obj);
// Build the member string
var members_str = obj.pre + "{{#invoke:" + obj.mod + "|" + obj.func + "|cols=" + obj.cols.join(",") + "\n";
for (var i in obj.struc) {
members_str += "|" + obj.struc[i].join("|");
members_str += parseInt(i)+1 == obj.struc.length ? "}}" : "\n";
}
members_str += obj.post;
// Make the request to update the wikitext for the Members page
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: {
format: 'json',
action: 'edit',
title: "Wikipedia:" + $("#vte-window").data("vte-project") + "/Members",
text: members_str, // will replace entire page content
summary: "[VTE] Adding member to project: " + $("#vte-members-add-name").val(),
token: mw.user.tokens.get( 'editToken' )
}
})
.done( function(data) {
// 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"),
view: "Members"
});
// Add the user to the members list
console.log("Added member: " + n);
$("#vte-window-right-content").append(
"<div class='vte-members-actions' vte-name='" + n + "' id='action_" + n + "'>" + action + "</div>"
);
if ("name" in c_map) {
$("#vte-window-right-content").append(
"<div id='vte-members-name-" + n + "' class='vte-members-name'>" + n + "</div>"
);
}
if ("member_since" in c_map) {
$("#vte-window-right-content").append(
"<div id='vte-members-date-" + n + "' class='vte-members-date'>" + vte.getDateStr(d) + "</div>"
);
}
if ("project_roles" in c_map) {
var roles = $("#vte-members-add-comment").val() ? $("#vte-members-add-comment").val() : " ";
$("#vte-window-right-content").append(
"<div id='vte-members-comment-" + n + "' class='vte-members-comment'>" + roles + "</div>"
);
}
$("#vte-window-right-content").append("<div style='clear: both;'/>");
// Add action to open Actions menu on click
$("#action_" + n).click(vte.clickMemberActions);
$(".vte-members-name").css(s_vteMembersName);
$(".vte-members-date").css(s_vteMembersDate);
$(".vte-members-comment").css(s_vteMembersComment);
$(".vte-members-actions").css(s_vteMembersAction);
// And clear out the member inputs
$("#vte-members-add-name").val("");
$("#vte-members-add-comment").val("");
})
.fail( function(xhr) {
console.error("Failed to update Members page: " + JSON.stringify(xhr));
});
});
},
parseTable: function(data, mod, func) {
// Parse out the module text, plus everything before and after it
var re = new RegExp("([\\s\\S]*)(\\{\\{#invoke:" + mod + "\\|" + func + "\\|[^\\}]+)\\}\\}([\\s\\S]*)");
var m = data.match(re);
if (m === null || m.length != 4) {
console.error("Failed to find module invocation ({#invoke:" + mod + "|" + func + ")");
console.error("Page contains: " + data);
return false;
}
var pre = m[1];
var post = m[3];
// Break apart the module, grabbing columns first and then parsing arguments
var m1 = m[2].match(/\|cols=([^\|]+)\|/);
if (m1 === null) {
console.error("Failed to find column declaration in module invocation: " + m[2]);
return false;
}
var cols = m1[1].split(",");
cols = cols.map( function(c) { return c.trim(); } );
// Grab the displayed_cols argument, if it exists
var m2 = m[2].match(/\|displayed_cols=([^\|]+)\|/);
var displayed_cols = [];
if (m2 !== null) {
displayed_cols = m2[1].split(",");
}
displayed_cols = displayed_cols.map( function(c) { return c.trim(); } );
// Then handle the rest of the arguments
var args = m[2].split("|");
// Remove the Module and function names
args = args.slice(2);
// Then store arguments in a 2d array representing table values
var struc = [];
var row = [];
for (var i in args) {
args[i] = args[i].trim();
if (args[i].slice(0,5) == "cols=" || args[i].slice(0,15) == "displayed_cols=") continue
row.push(args[i]);
if (row.length == cols.length) {
struc.push(row);
row = [];
}
}
var obj = {
pre: pre,
post: post,
mod: mod,
func: func,
cols: cols,
displayed_cols: displayed_cols,
struc: struc,
};
// Save the data for later
$("#vte-window").data(mod, obj);
return obj;
},
clickMemberActions: function(e) {
var name = $(e.currentTarget).attr("vte-name");
console.log("opening actions for user " + name);
$(".vte-members-action-div").remove();
// Draw the actions window, should support posting to user talk page, viewing contributions, and removing
var obj = $("#vte-window").data("Members");
var roles_action = obj.cols.indexOf("project_roles") != -1 ?
"<div id='vte-action-role' class='vte-members-actions-action' vte-name='" + name + "'>Edit user roles</div>" :
"";
// Get the status of the current user
var name_i = 0;
var stat = "";
var inactive_action = "";
if (obj.cols.indexOf("member_status") != -1) {
for (var i in obj.cols) { if (obj.cols[i].slice(0,4) == "name") name_i = i; }
for (var i in obj.struc) {
if (obj.struc[i][name_i] == name)
stat = obj.struc[i][ obj.cols.indexOf("member_status") ] == "Active" ? "inactive" : "active";
}
inactive_action =
"<div id='vte-action-remove' class='vte-members-actions-action' vte-name='" + name + "'>Set "+stat+"</div>";
}
$("#vte-window").append(
"<div class='vte-members-actions-div'>" +
" <div id='vte-action-message' class='vte-members-actions-action' vte-name='" + name + "'>Post to user talk</div>" +
roles_action +
" <div id='vte-action-user' class='vte-members-actions-action' vte-name='" + name + "'>View user contributions</div>" +
inactive_action +
"</div>"
);
$(".vte-members-actions-div").css(s_vteMembersActionsDiv);
$(".vte-members-actions-action").css(s_vteMembersActionsAction);
// Position the div by the cursor, relative to parent, considering the scroll
var x = (e.pageX - $('#vte-window').offset().left) - 150;
var y = (e.pageY - $('#vte-window').offset().top) + 10;
$(".vte-members-actions-div").css({ "left": x + "px", "top": y + "px" });
// Close the actions window on hitting escape or clicking outside the actions window
var opening = true;
var t = setTimeout(function() { opening = false; }, 200);
$(document).on('keyup.hide_member_actions', function(e) {
if (opening == false) {
if (e.keyCode == 27) {
console.log("Closing actions menu on escape.");
$(".vte-members-actions-div").remove();
$(document).unbind('keyup.hide_member_actions');
}
}
});
$(document).on('click.hide_member_actions', function(e) {
if (opening == false) {
console.log("Closing actions menu on click.");
$(".vte-members-actions-div").remove();
$(document).unbind('click.hide_member_actions');
}
});
// Add the post to user talk page action
$("#vte-action-message").on("click", function(e) {
// Remove the actions div
console.log("Removing the action div box");
$(".vte-members-actions-div").remove();
// Open the message box, should contain space for subject and the message
var name = $(e.currentTarget).attr("vte-name");
$("#vte-window-right-content").append(
"<div class='vte-members-actions-message'>" +
" <form id='vte-member-message' action='javascript();'>" +
" <div class='vte-members-actions-message-subject'>" +
" Subject: <input type='text' id='vte-message-subject' />" +
" </div>" +
" <div class='vte-members-actions-message-text'>" +
" Message: <textarea rows='10' cols='50' id='vte-message-text' />" +
" </div>" +
" <input type='submit' value='Send' />" +
" </form>" +
"</div>"
);
$(".vte-members-actions-message").css(s_vteMembersActionsMessage);
// Close the message window on hitting escape or (TODO) clicking the close button
$(document).on('keyup.hide_member_message', function(e) {
if (e.keyCode == 27) {
$(".vte-members-actions-message").remove();
$(document).unbind('keyup.hide_member_message');
}
});
// Add action to post to the talk page
$("#vte-member-message").submit(function(e) {
e.preventDefault();
console.log("Posting a message to user talk page: " + name);
// Pull the text from the message box
var text = "\n== " + $("#vte-message-subject").val() + " ==\n\n" + $("#vte-message-text").val() + "\n";
// Ensure the post is signed
if (text.match(/\~\~\~\~/) == null) text += "~~~~\n";
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: {
format: 'json',
action: 'edit',
title: "User_talk:" + name,
appendtext: text,
summary: "[VTE] Posting directed talk page message - " + $("#vte-message-subject").val(),
token: mw.user.tokens.get( 'editToken' )
}
})
.done( function(data) {
mw.notify( "Successfully posted to user's Talk page." );
console.log("Successfully posted to user's talk page");
$(".vte-members-actions-message").remove();
})
.fail( function(xhr) {
console.error("Failed to post to user talk page: " + JSON.stringify(xhr));
});
});
}); // End post to talk page vte-action-message click
// Add the update user role action
$("#vte-action-role").on("click", function(e) {
// Remove the actions div
console.log("Removing the action div box");
$(".vte-members-actions-div").remove();
// Open the role update dialogue, prepopulate with this member's current roles
var name = $(e.currentTarget).attr("vte-name");
var v = $("#vte-members-comment-" + name).html();
$("#vte-window-right-content").append(
"<div class='vte-members-actions-role'>" +
" <form id='vte-member-role' action='javascript();'>" +
" <div class='vte-role'>" +
" Project roles: <input type='text' id='vte-roles-input' value='" + v + "' />" +
" </div>" +
" <input type='submit' value='Update role' />" +
" </form>" +
"</div>"
);
$(".vte-members-actions-role").css(s_vteMembersActionsMessage); // TODO: Could clean this up
// Close the message window on hitting escape or TODO: clicking outside the window
var t = setTimeout(function() {
$(document).on('keyup.hide_role', function(e) {
if (e.keyCode == 27) {
$(".vte-members-actions-role").remove();
$(document).unbind('keyup.hide_role');
}
});
/*
$(document).on('click.hide_role', function(e) {
$(".vte-members-actions-role").remove();
$(document).unbind('click.hide_role');
});
*/
}, 100);
$("#vte-member-role").submit(function(e) {
e.preventDefault();
// Update the Members page first, then the VTE if we're successful.
var obj = $("#vte-window").data("Members");
var members_str = obj.pre + "{{#invoke:" + obj.mod + "|" + obj.func + "|cols=" + obj.cols.join(",") + "\n";
var name_i = 0;
for (var i in obj.cols) { if (obj.cols[i].slice(0,4) == "name") name_i = i; break; }
for (var i in obj.struc) {
if (obj.struc[i][name_i] == name)
obj.struc[i][ obj.cols.indexOf("project_roles") ] = $("#vte-roles-input").val();
members_str += "|" + obj.struc[i].join("|");
members_str += parseInt(i)+1 == obj.struc.length ? "}}" : "\n";
}
members_str += obj.post;
// Make the request to update the wikitext for the Members page
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: {
format: 'json',
action: 'edit',
title: "Wikipedia:" + $("#vte-window").data("vte-project") + "/Members",
text: members_str, // will replace entire page content
summary: "[VTE] Updating roles for user " + name +" in project: "+ $("#vte-window").data("vte-project"),
token: mw.user.tokens.get( 'editToken' )
}
})
.done( function(data) {
// 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"),
view: "Members"
});
console.log("Successfully updated member roles for " + name);
// Just update the roles for the current user
$("#vte-members-comment-" + name).html($("#vte-roles-input").val());
// And remove the roles dialogue
$(".vte-members-actions-role").remove();
// And save the members struc
$("#vte-window").data("Members", obj);
})
.fail( function(xhr) {
console.error("Failed to update member roles for " + name + ": " + JSON.stringify(xhr));
});
});
}); // End vte-action-role click
// Add the view user contributions action
$("#vte-action-user").on("click", function(e) {
e.preventDefault();
// Draw the lightbox that user stats will be placed within
var name = $(e.currentTarget).attr("vte-name");
var alt_name = name.replace(/_/g, " ");
$("#vte-window").append(
"<div id='vte-members-contribution'>" +
" <div id='vte-members-contribution-title'>" + name + " - User Contributions</div>" +
" <div id='vte-members-contribution-close'>" +
" <img src='/media/wikipedia/commons/6/60/Close_icon.svg' width='25' height='25'>"+
" </div>" +
" <div style='clear: both;'/>" +
" <div id='vte-members-contribution-edits'>" +
" Weely edits by namespace over time" +
" <div style='height: 10px; width: 100%; border-bottom: 1px solid #000;'></div>" +
" <div id='vte-loading-edits' class='vte-loading'>Loading user edits...</div>" +
" </div>" +
"</div>"
);
$("#vte-members-contribution").css(s_vteMembersContribution);
$("#vte-members-contribution-title").css({"float": "left"});
$("#vte-members-contribution-close").css({"float": "right", "cursor": "pointer"});
$(".vte-loading").css(s_vteLoadingText);
$("#vte-members-contribution-edits").css(s_vteMembersContributionEdits);
// Add action to close the window
$("#vte-members-contribution-close").click(function(e) {
$("#vte-members-contribution").remove();
});
// Add line graph of user edits, separated by namespace
var url = "https://alahele.ischool.uw.edu:8997/api/getEdits?user=" + alt_name + "&namespace=0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15&group=page|user|date";
$.ajax({
url: url,
dataType: "json",
success: function(data, stat, xhr) {
if (data.errostatus == "fail") {
console.error("Error: " + data.message);
return false;
}
console.log("Sucessfully fetched user edits.");
$("#vte-loading-edits").remove();
vte.drawUserEdits(data);
},
error: function(xhr, stat, err) {
console.error("Failed to request user edits: " + JSON.stringify(xhr));
},
complete: function() {
$("#vte-loading-edits").remove();
},
});
}); // End view user contributions vte-action-user click
// Add the set user inactive action
$("#vte-action-remove").on("click", function(e) {
e.preventDefault();
// Update the Members page first, then the VTE if we're successful.
var name = $(e.currentTarget).attr("vte-name");
var obj = $("#vte-window").data("Members");
var members_str = obj.pre + "{{#invoke:" + obj.mod + "|" + obj.func + "|cols=" + obj.cols.join(",") + "\n";
var name_i = 0;
var stat = "";
for (var i in obj.cols) { if (obj.cols[i].slice(0,4) == "name") name_i = i; break; }
for (var i in obj.struc) {
// If this is the user we're removing, update status and continue
stat = obj.struc[i][ obj.cols.indexOf("member_status") ] == "Active" ? "Inactive" : "Active";
if (obj.struc[i][name_i] == name)
obj.struc[i][ obj.cols.indexOf("member_status") ] = stat;
members_str += "|" + obj.struc[i].join("|");
members_str += parseInt(i)+1 == obj.struc.length ? "}}" : "\n";
}
members_str += obj.post;
// Make the request to update the wikitext for the Members page
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: {
format: 'json',
action: 'edit',
title: "Wikipedia:" + $("#vte-window").data("vte-project") + "/Members",
text: members_str, // will replace entire page content
summary: "[VTE] Removing user " + name +" from project members for project: "+ $("#vte-window").data("vte-project"),
token: mw.user.tokens.get( 'editToken' )
}
})
.done( function(data) {
// 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"),
view: "Members",
type: "status"
});
console.log("Successfully set project member as " + stat + ": " + name);
// Then update the display of the user
if (stat == "Inactive") {
$("#vte-members-name-" + name).css(s_vteMembersInactive);
$("#vte-members-date-" + name).css(s_vteMembersInactive);
$("#vte-members-comment-" + name).css(s_vteMembersInactive);
} else {
$("#vte-members-name-" + name).css(s_vteMembersName);
$("#vte-members-date-" + name).css(s_vteMembersDate);
$("#vte-members-comment-" + name).css(s_vteMembersComment);
}
// And save the new version of the members struc
$("#vte-window").data("Members", obj);
})
.fail( function(xhr) {
console.error("Failed to remove project member - " + name + ": " + JSON.stringify(xhr));
});
}); // End vte-action-remove click
},
drawTasks: function(data) {
console.log("in drawTasks");
// Clear the current content window
$("#vte-window-right-content").html("");
// Grab current task list from the Tasks page text
var obj = vte.parseTable(data, "TaskList", "printTasksTable");
// 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
// Possible column names given in [[Module:TaskList]] (although those aren't enforced).
var c_map = {};
for (var i in obj.cols) {
if (obj.cols[i] == "title") { c_map["title"] = i; }
else if (obj.cols[i] == "notes") { c_map["notes"] = i; }
else if (obj.cols[i] == "page") { c_map["page"] = i; }
else if (obj.cols[i] == "created") { c_map["created"] = i; }
else if (obj.cols[i] == "due") { c_map["due"] = i; }
else if (obj.cols[i] == "priority") { c_map["priority"] = i; }
else if (obj.cols[i] == "owner") { c_map["owner"] = i; }
else if (obj.cols[i] == "completed") { c_map["completed"] = i; }
else if (obj.cols[i] == "remaining") { c_map["remaining"] = i; }
else if (obj.cols[i] == "progress") { c_map["progress"] = i; }
else if (obj.cols[i] == "subtask") { c_map[ obj.cols[i] ] = i; }
else if (obj.cols[i] == "subcomplete") { c_map[ obj.cols[i] ] = i; }
else { console.warn("Unknown column: " + obj.cols[i]); }
}
// Make sure we have the required columns defined (title, priority, owner, created, due, etc)
// - These will be added to the Tasks page module invocation by the VTE if they don't exist.
// What gets displayed won't change since that depends on the project's invocation of
// the module with the displayed_cols argument.
if (! ("title" in c_map)) {
obj.cols.push("title"); c_map["title"] = obj.cols.length-1;
// Also add empty values in the tasks struc
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("priority" in c_map)) {
obj.cols.push("priority"); c_map["priority"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("page" in c_map)) {
obj.cols.push("page"); c_map["page"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("owner" in c_map)) {
obj.cols.push("owner"); c_map["owner"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("created" in c_map)) {
obj.cols.push("created"); c_map["created"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("due" in c_map)) {
obj.cols.push("due"); c_map["due"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("remaining" in c_map)) {
obj.cols.push("remaining"); c_map["remaining"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("completed" in c_map)) {
obj.cols.push("completed"); c_map["completed"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("notes" in c_map)) {
obj.cols.push("notes"); c_map["notes"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("subtask" in c_map)) {
obj.cols.push("subtask"); c_map["subtask"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
if (! ("subcomplete" in c_map)) {
obj.cols.push("subcomplete"); c_map["subcomplete"] = obj.cols.length-1;
for (var j in obj.struc) { obj.struc[j][obj.cols.length-1] = ""; }
}
// If we do have subtasks, make sure subcomplete matches the number of subtasks for each task and
// ensure the format is either 0 for undefined or 0 and 1 otherwise (ie, non-zero or null values
// indicate the task was completed)
for (var j in obj.struc) {
var n_sc = [];
var sc = obj.struc[j][c_map["subcomplete"]].split(",");
var st = obj.struc[j][c_map["subtask"]].split(",");
for (var k in st) {
if (typeof(sc[k]) === 'undefined' || sc[k] == 0 || sc[k] == "") {
n_sc[k] = 0;
} else {
n_sc[k] = 1;
}
}
obj.struc[j][c_map["subcomplete"]] = n_sc.join(",");
}
// And then update the tasks struc
$("#vte-window").data("TaskList", obj);
$("#vte-window").data("TaskList-c_map", c_map);
// Once we've parsed all the tasks from the Tasks page, draw the VTE Tasks content
// Add the create task button first
$("#vte-window-right-content").append(
"<input type='submit' class='vte-tasks-create' value='Create New Task' />"
);
// Display existing open tasks (header on top, only show columns that were saved on the project's Tasks page)
$("#vte-window-right-content").append(
"<table class='vte-open-tasks-table' cellpadding='0'>" +
" <colgroup>" +
" <col class='vte-open-tasks-table-title'>" +
" <col class='vte-open-tasks-table-priority'>" +
" <col class='vte-open-tasks-table-owners'>" +
" <col class='vte-open-tasks-table-created'>" +
" <col class='vte-open-tasks-table-due'>" +
" </colgroup>" +
" <thead>" +
" <tr class='vte-open-tasks-row-head'>" +
" <th class='oh vte-tasks-head-title'>Title</th>" +
" <th class='oh vte-tasks-head-priority'>Pri</th>" +
" <th class='oh vte-tasks-head-owner'>Owners</th>" +
" <th class='oh vte-tasks-head-created'>Created</th>" +
" <th class='oh vte-tasks-head-due'>Due</th>" +
" </tr>" +
" </thead>" +
" <tbody class='vte-open-tasks-table-body'/>" +
"</table>"
);
// Also display completed tasks (this table will be removed if no tasks have been completed)
$("#vte-window-right-content").append(
"<table class='vte-closed-tasks-table' cellpadding='0'>" +
" <colgroup>" +
" <col class='vte-closed-tasks-table-title'>" +
" <col class='vte-closed-tasks-table-priority'>" +
" <col class='vte-closed-tasks-table-owners'>" +
" <col class='vte-closed-tasks-table-created'>" +
" <col class='vte-closed-tasks-table-completed'>" +
" </colgroup>" +
" <thead>" +
" <tr class='vte-closed-tasks-row-head'>" +
" <th class='ch vte-tasks-head-title'>Title</th>" +
" <th class='ch vte-tasks-head-priority'>Pri</th>" +
" <th class='ch vte-tasks-head-owner'>Owners</th>" +
" <th class='ch vte-tasks-head-created'>Created</th>" +
" <th class='ch vte-tasks-head-due'>Due</th>" +
" </tr>" +
" </thead>" +
" <tbody class='vte-closed-tasks-table-body' />" +
"</table>"
);
var closed = 0;
//var link_reg = /\[\[([^\|\]]+).*?\]\]/g;
var link_reg = /\[\[:?([^\|\]]+)\|?(.*?)\]\]/g;
for (var i in obj.struc) {
var t = obj.struc[i][c_map["title"]];
var c = obj.struc[i][c_map["created"]];
var d = obj.struc[i][c_map["due"]];
var p = obj.struc[i][c_map["priority"]];
var o = obj.struc[i][c_map["owner"]];
// Attempt to parse the creation date
if (c) {
var m = c.match(/(\d+):(\d+), (\d+) (\S+) (\d+)/);
var date = m === null ? "" : new Date(m[5] + "-" + vte.getMonth(m[4]) + "-" + m[3] + " " + m[1] + ":" + m[2] + ":00 UTC");
if (date) c = vte.getDateStr(date);
}
// Attempt to parse the due date
if (d) {
var m = d.match(/^(\d{4})-(\d{2})-(\d{2})$/);
var date = m === null ? "" : new Date(d);
if (date) d = vte.getDateStr(date);
}
// Attempt to parse wiki links in the title
t = t.replace(link_reg, function(i, m) {
return "<a href='http://" + mw.config.get("wgContentLanguage") + ".wikipedia.org/wiki/" + m + "'>" + m + "</a>";
});
// Title is displayed first, then priority, owner, created, and due
if (! obj.struc[i][c_map["completed"]]) {
$(".vte-open-tasks-table-body").append(
"<tr id='task-" + i + "' class='vte-open-tasks-row' vte-task-index='" + i + "'>" +
" <td id='vte-open-tasks-table-title-" + i + "' class='vte-open-tasks-table-title vte-t-td'>" + t + "</td>" +
" <td id='vte-open-tasks-table-priority-" + i + "' class='vte-open-tasks-table-priority vte-t-td'>" + p + "</td>" +
" <td id='vte-open-tasks-table-owner-" + i + "' class='vte-open-tasks-table-owner vte-t-td'>" + o + "</td>" +
" <td id='vte-open-tasks-table-created-" + i + "' class='vte-open-tasks-table-created vte-t-td'>" + c + "</td>" +
" <td id='vte-open-tasks-table-due-" + i + "' class='vte-open-tasks-table-due vte-t-td'>" + d + "</td>" +
"</tr>"
);
} else {
closed = 1;
$(".vte-closed-tasks-table-body").append(
"<tr id='task-" + i + "' class='vte-closed-tasks-row' vte-task-index='" + i + "'>" +
" <td id='vte-closed-tasks-table-title-" + i + "' class='vte-closed-tasks-table-title vte-t-td'>" + t + "</td>" +
" <td id='vte-closed-tasks-table-priority-" + i + "' class='vte-closed-tasks-table-priority vte-t-td'>" + p + "</td>" +
" <td id='vte-closed-tasks-table-owner-" + i + "' class='vte-closed-tasks-table-owner vte-t-td'>" + o + "</td>" +
" <td id='vte-closed-tasks-table-created-" + i + "' class='vte-closed-tasks-table-created vte-t-td'>" + c + "</td>" +
" <td id='vte-closed-tasks-table-due-" + i + "' class='vte-closed-tasks-table-due vte-t-td'>" + d + "</td>" +
"</tr>"
);
}
}
// If not tasks have been closed, remove the closed tasks table
if (closed == 0) $(".vte-closed-tasks-table").remove();
// Style the tables
$(".vte-open-tasks-table, .vte-closed-tasks-table").css(s_vteTasksTable);
$(".vte-t-td").css(s_vteTasksRow);
$(".vte-open-tasks-table-title, .vte-closed-tasks-table-title").css(s_vteTasksTableTitle);
$(".vte-open-tasks-table-priority, .vte-closed-tasks-table-priority").css(s_vteTasksTablePriority);
$(".vte-open-tasks-table-created, .vte-closed-tasks-table-created").css(s_vteTasksTableCreated);
$(".vte-open-tasks-table-due, .vte-closed-tasks-table-due").css(s_vteTasksTableDue);
$(".oh, .ch").css({ "cursor": "pointer", "padding": "4px 0px" });
$(".vte-tasks-create").css(s_vteTasksCreate);
// Action to highlight row on hover
$(".vte-closed-tasks-row, .vte-open-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) {
e.preventDefault();
// Draw the lightbox
vte.drawTaskEdit();
}); // END submit new task
// Action to edit an existing task
$(".vte-open-tasks-row, .vte-closed-tasks-row").click(function(e) {
var index = $(e.currentTarget).attr("vte-task-index");
vte.drawTaskEdit(index);
});
},
drawChat: function() {
var project = $("#vte-window").data("vte-project");
// 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: $("#vte-window").data("vte-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");
// Clear the current content window and draw the chat form
$("#vte-window-right-content").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("TaskList");
var c_map = $("#vte-window").data("TaskList-c_map");
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' />";
} else { for (var i = 0; i < Object.keys(c_map).length; i++) task[i] = ""; }
// Draw the lightbox
$("#vte-window").append(
"<div id='vte-task-edit'>" +
" <div id='vte-task-close'>" +
" <img src='/media/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[c_map["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[c_map["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[c_map["priority"]] == 0 ? "SELECTED" : "") + ">0 (most urgent)</option>" +
" <option value='1' " + (task[c_map["priority"]] == 1 ? "SELECTED" : "") + ">1</option>" +
" <option value='2' " + (task[c_map["priority"]] == 2 ? "SELECTED" : "") + ">2</option>" +
" <option value='3' " + (task[c_map["priority"]] == 3 ? "SELECTED" : "") + ">3</option>" +
" <option value='4' " + (task[c_map["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[c_map["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[c_map["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[c_map["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 = task[c_map["owner"]].split(",");
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' />"
);
// Subtasks -
var subtasks = task[c_map["subtask"]].split(",");
var subcompletes = task[c_map["subcomplete"]].split(",");
for (var i in subtasks) {
if (subtasks[i] == "") continue;
$(".vte-task-edit-subtasks").append(
"<div class='vte-subtask-row'>" +
" <input type='checkbox' vte-subtask-index='" + i + "' class='vte-task-edit-subcomplete' />" +
" <div class='vte-task-edit-subtask'>" + subtasks[i] + "</div>" +
"</div>"
);
if (subcompletes[i] == 1) $("#vte-task-edit-subtask-" + i).prop("checked", true);
}
// 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 = task[c_map["owner"]].split(",").length;
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[c_map["owner"]].split(",").splice(index, 1);
$("[vte-owner-index='" + index + "']").remove();
}
function addSubtask() {
var index = task[c_map["subtask"]].split(",").length;
var $html = $(
"<div class='vte-subtask-row' vte-subtask-index='" + index + "'>" +
" <input type='checkbox' vte-subtask-index='" + index + "' class='vte-task-edit-subcomplete' />" +
" <div class='vte-task-edit-subtask'>" + $("#vte-task-subtask").val() + "</div>" +
"</div>"
);
$("#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 save action
$(".vte-task-save").button().click(function(e) {
});
// Handle the mark complete action
$(".vte-task-mark-complete, .vte-task-save").button().click(function(e) {
e.preventDefault();
var obj = $("#vte-window").data("TaskList");
var c_map = $("#vte-window").data("TaskList-c_map");
// Update the completed date if we've clicked Mark Complete
if ($(e.currentTarget).attr("value") == "Mark Complete") {
var d = new Date();
obj.struc[index][c_map["completed"]] = String(d.getFullYear()) + "-" + String(vte.pad( parseInt(d.getMonth()) + 1, 2)) + "/" + String(vte.pad(d.getDate(), 2));
}
// Build the module invocation string
var tasks_str = obj.pre + "{{#invoke:" + obj.mod + "|" + obj.func + "|cols=" + obj.cols.join(",") + "\n";
if (obj.displayed_cols.length > 0) {
tasks_str += "|displayed_cols=" + obj.displayed_cols.join(",") + "\n";
}
// If this is a new task, add it to the obj.struc
if (typeof(index) === 'undefined') {
task[c_map["created"]] = "~~~~~";
obj.struc.push(task);
index = obj.struc.length-1;
}
for (var i in obj.struc) {
if (i == index) {
obj.struc[i][c_map["title"]] = $("#vte-task-title").val();
obj.struc[i][c_map["page"]] = $("#vte-task-page").val();
obj.struc[i][c_map["priority"]] = $("#vte-task-priority").val();
obj.struc[i][c_map["remaining"]] = $("#vte-task-remaining").val();
obj.struc[i][c_map["due"]] = $("#vte-task-due").val();
var owner = [];
$(".vte-task-edit-owner").each(function() {
owner.push($(this).html());
});
obj.struc[i][c_map["owner"]] = owner.join(",");
var subtask = [];
$(".vte-task-edit-subtask").each(function() {
subtask.push($(this).html());
});
obj.struc[i][c_map["subtask"]] = subtask.join(",");
var subcomplete = [];
$(".vte-task-edit-subcomplete").each(function() {
subcomplete.push( $(this).prop("checked") == true ? 1 : 0 );
});
obj.struc[i][c_map["subcomplete"]] = subcomplete.join(",");
obj.struc[i][c_map["notes"]] = $("#vte-task-notes").val();
}
tasks_str += "|" + obj.struc[i].join("|");
tasks_str += parseInt(i) + 1 == obj.struc.length ? "}}" : "\n";
}
tasks_str += obj.post;
// Make the request to update the text for the Tasks page
$.ajax({
url: mw.util.wikiScript( 'api' ),
type: 'POST',
dataType: 'json',
data: {
format: 'json',
action: 'edit',
title: "Wikipedia:" + $("#vte-window").data("vte-project") + "/Tasks",
text: tasks_str, // will replace entire page content
summary: "[VTE] Updating details for task: " + $("#vte-task-title").val(),
token: mw.user.tokens.get( 'editToken' )
}
})
.done( function(data) {
// 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"),
view: "Tasks",
});
console.log("Successfully updated details for task: " + $("#vte-task-title").val());
mw.notify( "Successfully updated task: " + $("#vte-task-title").val() + "." );
$("#vte-tasks").click();
$("#vte-task-edit").remove();
})
.fail( function(xhr) {
console.error("Failed to update details for task: " + JSON.stringify(xhr));
mw.notify( "Failed to update details for task: " + JSON.stringify(xhr));
});
});
// 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"});
},
// populateContent - draws the main content when no projects are selected (project browse)
populateContent: function() {
// Loading text
$("#vte-window-right-content").html(
"<div class='vte-loading'>Loading project content...</div>"
);
$(".vte-loading").css(s_vteLoadingText);
// Request the most active projects
var url = "https://alahele.ischool.uw.edu:8997/api/getActiveProjects?group=project|namespace";
$.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;
}
// Parse the data here, otherwise it will be re-parsed every time we sort
var struc = {};
for (var i in data.result) {
if (! (data.result[i].p_id in struc)) {
struc[data.result[i].p_id] = {
p_title: data.result[i].p_title, p_id: data.result[i].p_id,
total_edits: 0, total_pages: 0, p_created: data.result[i].p_created,
total_project_pages: 0,
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, 100: 0, 101: 0, 108: 0,
109: 0, 118: 0, 119: 0, 446: 0, 447: 0, 710: 0,
711: 0, 828: 0, 829: 0, 2600: 0,
};
}
struc[data.result[i].p_id][data.result[i].pa_page_namespace] += data.result[i].edits;
struc[data.result[i].p_id]["total_edits"] += data.result[i].edits;
struc[data.result[i].p_id]["total_pages"] += data.result[i].pages;
if (data.result[i].pa_page_namespace == 4 || data.result[i].pa_page_namespace == 5)
struc[data.result[i].p_id]["total_project_pages"] += data.result[i].pages;
}
var a_struc = [];
for (var p in struc) {
// Add the ratio and push to the array
struc[p].ratio = struc[p].total_edits / struc[p].total_pages;
a_struc.push(struc[p]);
}
data.result = a_struc;
// If we successfully fetched active projects, draw them in vte-summary-projects
vte.drawSummaryContent(data, "edits");
},
error: function(xhr, stat, err) {
console.error("Failed to request active projects: " + JSON.stringify(xhr));
}
});
},
drawSummaryContent: function(data, sort_by) {
// We can sort if we want to..
if (typeof(sort_by) !== 'undefined') {
if (sort_by == 'edits') {
if (data.result[0].total_edits < data.result[ data.result.length-1 ].total_edits) {
data.result.sort( function(a,b) { return a.total_edits - b.total_edits } );
} else {
data.result.sort( function(a,b) { return b.total_edits - a.total_edits } );
}
} else if (sort_by == 'pages') {
if (data.result[0].total_pages < data.result[ data.result.length-1 ].total_pages) {
data.result.sort( function(a,b) { return a.total_pages - b.total_pages } );
} else {
data.result.sort( function(a,b) { return b.total_pages - a.total_pages } );
}
} else if (sort_by == 'ratio') {
if (data.result[0].ratio < data.result[ data.result.length-1 ].ratio) {
data.result.sort( function(a,b) { return a.ratio - b.ratio } );
} else {
data.result.sort( function(a,b) { return b.ratio - a.ratio } );
}
} else if (sort_by == 'project_edits') {
if (data.result[0]["4"] < data.result[data.result.length-1]["4"]) {
data.result.sort( function(a,b) { return a["4"] - b["4"] } );
} else {
data.result.sort( function(a,b) { return b["4"] - a["4"] } );
}
} else if (sort_by == 'project_pages') {
if (data.result[0].total_project_pages < data.result[data.result.length-1].total_project_pages) {
data.result.sort( function(a,b) { return a.total_project_pages - b.total_project_pages } );
} else {
data.result.sort( function(a,b) { return b.total_project_pages - a.total_project_pages } );
}
}
}
// Clear the content div, print initial greeting
$("#vte-window-right-content").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' 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
$.each(data.result, 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>"
);
// 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", proj.p_title);
$("#vte-window").data("vte-project-attr", {
id: $(e.currentTarget).attr("p_id"),
title: $(e.currentTarget).attr("p_title"),
created: $(e.currentTarget).attr("p_created"),
});
$("#vte-project-select-input").val( $(e.currentTarget).attr("p_title").replace(/_/g," ") );
// Style the input
$("#vte-project-select-input").prop("disabled", true);
$("#vte-project-select-input").css("color", "#A4A4A4");
// Draw the page
vte.populateNav();
vte.drawProjectSummary();
});
});
// 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");
vte.drawSummaryContent(data, sort_by);
});
},
//
// HELPER FUNCTIONS
//
// 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();
},
// 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.
getDateStr: function(d) {
if (typeof(d) === 'undefined') {
d = new Date();
}
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) {
var 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];
},
};
/**** 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_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,.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)",
"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 #EEE",
"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",
};
// 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": s_wpFont,
"font-size": "10px",
"margin": "5px 0px 5px 10px",
"color": "#424242",
};
var s_vteTasksTable = {
"font-family": s_wpFont,
"font-size": "12px",
"color": "#424242",
"border-collapse": "collapse",
};
var s_vteTasksRow = {
"border-bottom": "1px solid #EEE",
"cursor": "pointer",
"padding": "3px 0px",
};
var s_vteTasksTableTitle = {
"width": "40%",
};
var s_vteTasksTablePriority = s_vteTasksTableCreated = s_vteTasksTableDue = {
"text-align": "center",
};
s_vteTasksTablePriority["width"] = "5%";
s_vteTasksTableCreated["width"] = "20%";
s_vteTasksTableDue["width"] = "15%";
// 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_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_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,
"font-size": "10px",
"font-style": "italic",
"border-bottom": "solid 1px #EEE",
"padding-top": "10px",
};
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)
//if (mw.config.get('wgCanonicalNamespace') === 'Wikipedia') {
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("https://alahele.ischool.uw.edu:8997", {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>