Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/* Add any or all of these lines after the importScript line to set various options (the default values are shown):

	var todo_portlet = "p-personal";           // Defines which portlet menu the link is added to - see [[Help:Customizing toolbars]] for options (choose one of the valid values for portletId)
	var todo_subpage = "todo";                 // Subpage where the to-do list entry is to be added
	var todo_viewlabel = "View ToDo";          // Custom label for the link. Replace quoted text with your desired name.
	var todo_addlabel = "Add to ToDo";         // Custom label for the link. Replace quoted text with your desired name.
	var todo_addfirst = null;                  // Replace null with any value, e.g. "yes" (including quotation marks) to show the Add link before the View link.
	var todo_viewnew = null;                   // Replace null with any value, e.g. "yes" (including quotation marks) to make the View link open in a new tab or window.

*/

// <nowiki>
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'ext.gadget.libExtraUtil']).then( function() {

// Set default options for any that haven't been set
var getOption = function getOptionFn(option, val) {
	return ( window[option] === undefined ) ? val : window[option];
};

var getConfig = function () {
	return {
		portlet:	getOption('todo_portlet', 'p-personal'),
		subpage:	getOption('todo_subpage', 'todo'),
		viewlabel:	getOption('todo_viewlabel', 'View ToDo'),
		addlabel:	getOption('todo_addlabel', 'Add to ToDo'),
		addfirst:	getOption('todo_addfirst', null),
		viewnew:	getOption('todo_viewnew', null),
		mw:			mw.config.get(['wgNamespaceNumber', 'wgPageName', 'wgUserName']),
		api:		new mw.Api( {
						ajax: {
							headers: { 
								'Api-User-Agent': 'ToDoLister/2.0 ( https://en.wikipedia.org/wiki/User:Evad37/ToDoLister )'
							}
						}
					} )
	};
};


var addItemToList = function (config) {
	var comment = prompt("Enter a comment for the to-do list");	
	if ( comment === null ) {
		return;
	}
	// Colon needed for File & Category namespaces
	var colonIsNeeded = ( config.mw.wgNamespaceNumber == 6 || config.mw.wgNamespaceNumber == 14 );
	var listItem = '\n<li id="{{subst:CURRENTTIMESTAMP}}" class="todolistitem">[[' +
		( colonIsNeeded ? ':' : '' ) + '{{subst:#titleparts:' + config.mw.wgPageName +
		'}}]] ~~~~~ ' + comment + '</li>';

	//Perform edit to add entry
	config.api.postWithToken('edit', {
		action: 'edit',
		title: 'User:' + config.mw.wgUserName + '/' + config.subpage,
		appendtext: listItem,
		summary: '[[w:en:User:Evad37/ToDoLister|ToDoLister]] adding 1 item: [[' + config.mw.wgPageName + ']]'
	})
	.done(function() {
		alert( "Added successfully" );
	})
	.fail( function(code, jqxhr) {
		alert(extraJs.makeErrorMsg(code, jqxhr));
	} );
};


/* == Remove items from Todo list == */
	
var queryPage = function (config, entryID) {
	return $.when( config.api.get( {
		action: 'query',
		titles: 'User:' + config.mw.wgUserName + '/' + config.subpage,
		prop: 'revisions|info',
		rvprop: 'content',
		indexpageids: 1,
		rawcontinue: ''
	}), entryID, config);
};

var getWikitext = function (result, entryID, config) {
	var pageid = result[0].query.pageids;
	var wikitext = result[0].query.pages[pageid].revisions[0]['*'];
	return $.Deferred().resolve(wikitext, entryID, config);
};

var checkForItem = function (wikitext, entryID, config) {
	if ( wikitext.indexOf('<li id="' + entryID + '"') < 0 ) {                                       
		return $.Deferred().reject('Could not locate entry in wikitext.');
	}
	return $.Deferred().resolve(wikitext, entryID, config);
};

var updateWikitext = function (wikitext, entryID, config) {	
	var startOfItemToRemove = wikitext.indexOf("<li id=\"" + entryID + "\"");
	var endOfItemToRemove = wikitext.indexOf("</li>", startOfItemToRemove) + 6; // 6 = character in closing tag plus a newline
	var updatedWikitext = wikitext.substr(0, startOfItemToRemove) + wikitext.substr(endOfItemToRemove);

	var startOfPageRemoved = wikitext.indexOf("[[", startOfItemToRemove);
	var endOfPageRemoved = wikitext.indexOf("]]", startOfItemToRemove) + 2; // +2 accounts for the closing square brackets
	var pageRemoved = wikitext.substring(startOfPageRemoved, endOfPageRemoved);
	
	return $.Deferred().resolve(updatedWikitext, pageRemoved, config);
};

var editPage = function (updatedWikitext, pageRemoved, config) {
	return config.api.postWithToken('edit', {
		action: 'edit',
		title: 'User:' + config.mw.wgUserName + '/' + config.subpage,
		text: updatedWikitext,
		summary: '[[w:en:User:Evad37/ToDoLister|ToDoLister]] removing 1 item: ' + pageRemoved
	} );
};

removeEntry = function (entryID, config) {
	//first check click was intended
	var confirmation = confirm("Are you sure you want to remove this item?");
	if ( !confirmation ) {
		return;
	}
	
	queryPage(config, entryID)
	.then(getWikitext)
	.then(checkForItem)
	.then(updateWikitext)
	.then(editPage)
	.done( function() {
		alert( "Removed successfully" );
		location.reload();
	} )
	.fail( function(code, jqxhr) {
		alert(extraJs.makeErrorMsg(code, jqxhr));
	} );
};


var addScriptLinks = function (config) {
	//insert view link
	var viewLink = mw.util.addPortletLink(
		config.portlet,
		'/wiki/User:' + config.mw.wgUserName + '/' + config.subpage,
		config.viewlabel,
		'todo_view'
	);
	if ( config.viewnew !== null ) {
		$(viewLink).find('a').attr('target', '_blank');
	}
	
	//insert add link
	var addLink = mw.util.addPortletLink(
		config.portlet,
		'#',
		config.addlabel,
		'todo_add',
		null,
		null,
		( config.addfirst !== null ) ? '#todo_view' : null
	);
	$(addLink).click(function(e) {
		e.preventDefault();
		addItemToList(config);
	});

	//Show remove links on todo list
	$('li.todolistitem').each(function() {
		var id = this.id;
		$('<span>')
		.css('margin-left', '0.5em')
		.append([
			"(",
			$('<a>')
			.text('remove')
			.css('cursor', 'pointer')
			.click(function() {
				removeEntry(id, config);
			}),
			")"
		])
		.appendTo(this);
	});
};

/* == Setup == */
addScriptLinks( getConfig() );

});
// </nowiki>