Wikiwand AI

User talk:The Transhumanist/RedlinksRemover.js

From Wikipedia, the free encyclopedia

This script processes bulleted lists, removing the redlinked end nodes, reiteratively, until none are left. (A redlinked end node is a list item that is comprised of nothing more than a redlink, and that has no children.) After it has done that, this script delinks the remaining red links, and deletes red category links. It doesn't remove list item entries that have annotations, or that have children (indented entries beneath it).

This is the workshop support page for the user script RedlinksRemover.js. Comments and requests concerning the program are most welcome. Please post discussion threads below the section titled Discussions. Thank you. By the way, the various scripts I have written are listed at the bottom of the page.[1]
This script is functional

Script's workshop

This is the work area for developing the script and its documentation. The talk page portion of this page starts at #Discussions, below.

Description / instruction manual

This script is functional

This script processes bulleted lists, removing the redlinked end nodes, reiteratively, until none are left. (A redlinked end node is a list item that is comprised of nothing more than a redlink, and that has no children.) After it has done that, this script delinks the remaining red links, and deletes red category links. It doesn't remove list item entries that have annotations, or that have children (indented entries beneath it).

The redlink remover has two major uses (but it is not limited to these):

  1. It can help clean up outlines that have accumulated too many redlinks.
  2. It simplifies creation of outlines using standard templates. A problem with outline generation templates is that they include every possible link that a particular type of topic (say, provinces, or cities) might have, which creates outlines with lots of red links. Following up outline creation with this script will solve that problem. Tip: it is best to work on the outline with redlinks for awhile before using the redlink remover, because the script will delink those redlinks that have children, leaving them in as informative branches in the outline. Removing redlinks too early creates extra work as many of the topics may need to be added back in or relinkified.

How to install this script

Important: this script was developed for use with the Vector skin (it's Wikipedia's default skin), and might not work with other skins. See the top of your Preferences appearance page, to be sure Vector is the chosen skin for your account.

To install this script, add this line to your vector.js page:

importScript("User:The Transhumanist/RedlinksRemover.js");

Save the page and bypass your cache to make sure the changes take effect. By the way, only logged-in users can install scripts.

Explanatory notes (source code walk-through)

This section explains the source code, in detail. It is for JavaScript programmers, and for those who want to learn how to program in JavaScript. Hopefully, this will enable you to adapt existing source code into new user scripts with greater ease, and perhaps even compose user scripts from scratch.

You can only use so many comments in the source code before you start to choke or bury the programming itself. So, I've put short summaries in the source code, and have provided in-depth explanations here.

My intention is Threefold:

  1. to thoroughly document the script so that even relatively new JavaScript programmers can understand what it does and how it works, including the underlying programming conventions. This is so that the components and approaches can be modified, or used again and again elsewhere, with confidence. (I often build scripts by copying and pasting code that I don't fully understand, which often leads to getting stuck). To prevent getting stuck, the notes below include extensive interpretations, explanations, instructions, examples, and links to relevant documentation and tutorials, etc. Hopefully, this will help both you and I grok the source code and the language it is written in (JavaScript).
  2. to refresh my memory of exactly how the script works, in case I don't look at the source code for weeks or months.
  3. to document my understanding, so that it can be corrected. If you see that I have a misconception about something, please let me know!

In addition to plain vanilla JavaScript code, this script relies heavily on the jQuery library.

If you have any comments or questions, feel free to post them at the bottom of this page under Discussions. Be sure to {{ping}} me when you do.

Aliases

An alias is one string defined to mean another. Another term for "alias" is "shortcut". In the script, the following aliases are used:

$ is the alias for jQuery (the jQuery library)

mw is the alias for mediawiki (the mediawiki library)

These two aliases are set up like this:

( function ( mw, $ ) {}( mediaWiki, jQuery ) );

That also happens to be a "bodyguard function", which is explained in the section below...

Bodyguard function

The bodyguard function assigns an alias for a name within the function, and reserves that alias for that purpose only. For example, if you want "t" to be interpreted only as "transhumanist".

Since the script uses jQuery, we want to defend jQuery's alias, the "$". The bodyguard function makes it so that "$" means only "jQuery" inside the function, even if it means something else outside the function. That is, it prevents other javascript libraries from overwriting the $() shortcut for jQuery within the function. It does this via scoping.

The bodyguard function is used like a wrapper, with the alias-containing source code inside it, typically, wrapping the whole rest of the script. Here's what a jQuery bodyguard function looks like:

1 ( function($) {
2     // you put the body of the script here
3 } ) ( jQuery );

See also: bodyguard function solution.

To extend that to lock in "mw" to mean "mediawiki", use the following (this is what the script uses):

1 ( function(mw, $) {
2     // you put the body of the script here
3 } ) (mediawiki, jQuery);

For the best explanation of the bodyguard function I've found so far, see: Solving "$(document).ready is not a function" and other problems   (Long live Spartacus!)

Load dependencies

Many of my scripts create menu items using mw.util.addPortletLink, which is provided in a resource module. Therefore, in those scripts it is necessary to make sure the supporting resource module (mediawiki.util) is loaded, otherwise the script could fail (though it could still work if the module happened to already be loaded by some other script). To load the module, use mw.loader, like this:

// For support of mw.util.addPortletLink
mw.loader.using( ['mediawiki.util'], function () {
// Body of script goes here.
} );

mw.loader.using is explained at mw:ResourceLoader/Core modules#mw.loader.using.

For more information, see the API Documentation for mw.loader.

The ready() event listener/handler

The ready() event listener/handler makes the rest of the script wait until the page (and its DOM) is loaded and ready to be worked on. If the script tries to do its thing before the page is loaded, there won't be anything there for the script to work on (such as with scripts that will have nowhere to place the menu item mw.util.addPortletLink), and the script will fail.

In jQuery, it looks like this: $( document ).ready(function() {});

You can do that in jQuery shorthand, like this:

$().ready( function() {} );

Or even like this:

$(function() {});

The part of the script that is being made to wait goes inside the curly brackets. But you would generally start that on the next line, and put the ending curly bracket, closing parenthesis, and semicolon following that on a line of their own), like this:

1 $(function() {
2     // Body of function (or even the rest of the script) goes here, such as a click handler.
3 });

This is all explained further at the jQuery page for .ready()

For the plain vanilla version see: http://docs.jquery.com/Tutorials:Introducing_$(document).ready()

var

This is the reserved word var, which is used to declare variables. A variable is a container you can put a value in. To declare the variable portletlink, write this:

var portletlink

A declared variable has no value, until you assign it one, such as like this:

portletlink = "yo mama";

You can combine declaration and assignment in the same statement, like this:

var portletlink = mw.util.addPortletLink('p-tb', '#', 'Remove red links');

Caveat: if you assign a value to a variable that does not exist, the variable will be created automatically. If it is created outside of a function, it will have global scope. For user scripts used on Wikipedia, having a variable of global scope means the variable may affect other scripts that are running, as the scripts are technically part of the same program, being called via import from a .js page (.js pages are programs). So, be careful. Here are some scope-related resources:

This adds a menu item to one of MediaWiki's menus. Use "p-tb" to signify the toolbox menu on the sidebar menu.

First you stick it in a variable, for example, "portletlink":

var portletlink = mw.util.addPortletLink('p-tb', '#', 'Remove redlinks');

It has up to 7 parameters. Only 3 are used above.

General usage:

mw.util.addPortletLink( 'portletId', 'href', 'text', 'id', 'tooltip', 'accesskey', 'nextnode');

It's components:

  • mw.util.addPortletLink: the ResourceLoader module to add links to the portlets.
  • portletId: the id of the portlet (that is, menu) where the new menu item is to be placed. The various menus ("portlets") are::
    • p-navigation: Navigation section in left sidebar
    • p-interaction: Interaction section in left sidebar
    • p-tb: Toolbox section in left sidebar
    • coll-print_export: Print/export section in left sidebar
    • p-personal Personal toolbar at the top of the page
    • p-views Upper right tabs in Vector only (read, edit, history, watch, etc.)
    • p-cactions Drop-down menu containing move, etc. (in Vector); subject/talk links and action links in other skins
  • href: Link to a Wikipedia or external page (the initial purpose of portletlink was to link somewhere)
  • text: Text that displays in the menu (the title of the
  • id: HTML id (optional)
  • tooltip: Tooltip to display on mouseover (optional)
  • accesskey: Shortcut key press (optional)
  • nextnode: id of the existing portlet link to place the new portlet link before (optional) (Don't forget: ids have a leading "#")

The optional fields must be included in the above order. To skip a field without changing it, use the value null, that is, no space between the quotes for that parameter.

To place the menu items in alphabetical order, and so that they don't move around in the menu, for your last menu item specify the id of an existing menu item to anchor it. Then set "next node" for the next to last item as the id for the menu item you just set, and so on.

See the complete documentation at https://www.mediawiki.org/wiki/ResourceLoader/Modules#addPortletLink and Help:Customizing toolbars.

Important: All we've done so far above is assign mw.util.addPortletLink to a variable. It won't do anything until we bind the variable to a click handler (see below).

click handler

To make a menu item that does something when you click on it, you have to "bind" mw.util.addPortletLink, via its variable, to a handler. Like this:

(The variable used in this example is "portletlink").

1 $(portletlink).click( function(e) {
2     e.preventDefault();
3     //do some stuff
4 }

The "handler" is the part between the curly brackets.

To read about function(e), see what does e mean in this function definition?
jQuery's event objects are explained here: http://api.jquery.com/category/events/event-object/
e.preventDefault() is short for event.preventDefault(), one of jQuery's event objects.

What is the default being prevented? Portletlink's default action is to link somewhere. We don't want it to do that, and so that is what e.preventDefault(); is for.

Calling a function

In JavaScript, a function is a subroutine, essentially, a program within the main program. Functions are usually placed at the end of the program, after its core, but can also be located in a library, like jQuery. You call a function by its name. The function "example" is called like this:

example();

See also: JavaScript Function Invocation.

window.location.href

window.location.href returns the current URL.

The window object represents the current window in the browser, and is at the top of the Browser Object Model hierarchy.

The location object pertains to the URL of the current document, and href is one of its properties.

window.location.href.indexOf

This applies the indexof method upon the URL, to return the index (starting position) of a given string. This can be used to check if the URL contains a specific string.

if (window.location.href.indexOf('action') >= 0 essentially means "if 'action' is in the URL". That is, its position in the URL is equal or greater than 0 (0 represents the first spot, 1 is the second spot, etc.), telling us that it is in there. If it is not there, it would return a -1.

window.location.href.substr

Gets part of the URL.

The substr method returns the substring from the provided start and end indexes, from within the string the method is applied to. If only a start index is provided, the substring will be from that index to the end of the string. In this case, the string is window.location.href (that is, the URL). Note that 0 represents the first character of the string.

So, window.location.href.substr(0,6) would return the first 7 characters of the URL.

That's not particularly useful, as we probably want to manipulate the string based on what is in it. For example...

window.location.href.substr(0, window.location.href.indexOf('#'))

What that returns is the beginning of the URL through the # character, which we can in turn use in concatenation. The following line of code concatenates (adds) ?action=edit to the substring, and then replaces the URL with it:

window.location = window.location.href.substr(0, window.location.href.indexOf('#'))+"?action=edit";

This jumps to the edit page for the current page, as if we clicked on "Edit".

This line assigns the variable redlinks to an empty array (represented by opening and closing square brackets).

Arrays are ordered sets of items.

We created this array to store all the redlinks that are on the page. (See below).

document.getElementsByTagName

The following line of code declares and assigns to the variable "a" all the elements in the document with the tag "<a>", creating an array:

var a = document.getElementsByTagName('a');

using a for loop to process an array

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Loops_and_iteration#for_statement

getAttribute('class')

This method returns the value of the attribute specified for an element it is attached to (with a dot, for example someElement.getAttribute('attribute')). This allows elements to be processed by a particular attribut, such as their class.

https://www.w3schools.com/jsref/met_element_getattribute.asp

https://www.w3schools.com/jsref/met_element_getattribute.asp

https://www.w3schools.com/html/html_attributes.asp

.length

.href.replace

.replace

decodeURIComponent

localStorage

This didn't work:

localStorage.OLUtils_redlinks = JSON.stringify(redlinks);

So I used this, and it worked:

jsonString = JSON.stringify(redlinks);
localStorage.OLUtils_redlinks = jsonString;

JSON.stringify() method

JSON.stringify()

Difference between JSON.stringify and JSON.parse

alert()

alert() is short for "window.alert()".

This command makes a message box with a message appear, with an OK button. The script will not continue until the OK button is pushed.

The message is included within the parentheses. It can be a string, a variable, or an object. If it is a variable or an object, its value or contents is displayed in the message.

JSON.parse() method

JSON.parse()

Difference between JSON.stringify and JSON.parse

RegExp

Change log

  • 2017-02-09
    • Started script with some pseudocode and feature wish list
    • Added:
      • importScript('User:AlexTheWhovian/script-functions.js');
      • copy/paste User:AlexTheWhovian/script-redlinks.js
    • Removed importScript('User:AlexTheWhovian/script-functions.js')
      • AlexTheWhovian said it wasn't used by script-redlinks.js
  • 2017-02-10 & 2017-02-11
    • In the process of documenting the script with detailed comments.
      • Got down through mw.util.addPortletLink
    • Added "Explanatory notes" section to the talk page, to provide more in-depth scripting support than the comments. Got to mw.util.addPortletLink
  • 2017-02-12
    • Fixed bug that prevented operation and required another version being loaded.
      • It was working weird because of a function invocation being placed out of context, at the start of the script.
      • There was also a function invocation missing from a conditional in the body of the script.
      • This script now runs stand-alone (without the crutch of the other version being run)
    • Changed the menu item to "Remove red links" (it was "remove redlinks")
  • 2017-02-14
    • Add ready function
  • 2017-02-15
    • Wrote pseudocode in script for bullet list item processing.
  • 2017-04-06
    • Worked out incrementing structure for the while/for nested loop pair for processing bullet list items.

Task list

  • Start script  Done
  • Add AlexTheWhovian's redlink script and function library  Done
  • Test it to see if it works  Done (it does not work = Dec 26 2016 15:08 version)
    • Cycle through various versions to see which work with Firefox  Done
      • Feb 28 2015 version  Done (it works)
      • May 27 2016 version  Done (it works)
      • Dec 4 2016 version  Done (it works)
      • Dec 22 2016 version  Done (failed - it appeared in menu, but failed to remove redlinks)
      • Dec 26 2016 15:05 version  Done (it works = chose this one for starting point)
      • Dec 26 2016 15:08 version  Done (failed - no menu item)
  • Determine which of the functions in User:AlexTheWhovian/script-functions.js are called in redlinks.js  Done (none)
    • If none, remove it  Done
  • Test local storage feature  Done
    • With alerts  Done
  • Fix red link removal so it works in originally intended fashion  Done
  • Implement nested loop to remove unannotated non-parent bulleted list entries
    • Situate a for loop inside a while loop  Done
    • Work out increment structure ((done))
    • Write the guts of the for loop (the regex for removing end of branch redlinks without annotation)
      • Study the regex objects used in the forked block of code
      • Review how to match a multiple-line string
  • Figure out why the "outline in title" alert in the function redlinks_removal() gets activated
    • Clear the local memory and test it
    • Study program flow
  • Wrap local storage in a try catch
  • Needs more comments, and more detailed comments
  • Write explanatory notes, on talk page, explaining the programming in-depth
  • Change the title to indicate the single function (redlink removal). Make a separate multi-function script.

Bug reports

  • Missing dependencies? (fixed 2017-02-12  Done - Fixed it wasn't dependencies, it was misplaced and missing function calls.
    • The script won't work. Fixed But there's a weird work-around:
      1. Run the Feb 28 2016 version at the same time
      2. Use it on a page with redlinks
      3. Go to your RedlinksRemover.js and bypass the cache
        Resolved
        – don't need work-around any more
  • 2017-02-13 Red link removal stopped working Fixed
    • The script puts the target page into edit mode, but then doesn't edit anything Fixed
  • 2017-04-06 The script runs the functions at the end of the script, when the "Remove red links" menu has not been clicked, and I don't know why

Desired/completed features

Completed features are marked with  Done
  • Remove redlinked entries in outlines
    • Remove redlinked bullet entries that both have no annotation and have no children. (If one has an annotation, or a child, don't remove it.) Because this could create new candidates, this function needs to be looped.
      • Check for annotation
      • To check for children, see if any bullet entries that follow it have more bullets than it does
      • If no changes are made during a complete loop, stop. (How do you check for changes?)
      • To prevent infinite looping, stop after 10 iterations (it can always be run again)
    • When no more candidates are to be are to be found, remove redcats, and delink the remaining redlinks.
  • Save title to variable.  Done don't have to. Can check title directly.
    • Some features will work only on outlines, and will check the title variable for "Outline of" first.  Done used
    • (if match "Outline of" in title, then do....)  Done used if (document.title.indexOf("Outline ") != -1) {}
  • Integrate anno.js (the annotation toggler).
    • get it working right first
  • For stream editing commands, the script will have an optional interactive mode.
  • For Macro compatibility, all toggles will have an on-"button" and an off-"button".
  • Entry linker (checks unlinked entry names for the existence of non-disambiguation page article titles. If one exists, linkify it.)
  • Entry inserter (checks template for entries missing in the current outline, then checks each title for existence.
  • If one exists, insert it, but not if it is a disambiguation page.)
  • Display a random outline, but not if currently in edit mode.
  • Display next outline in the main list of outlines, but not if currently in edit mode.

Development notes

Trycatch needed, and more

The Transhumanist, where you use local storage.getItem() or setItem() you should always wrap that in try catch, as it can fail at any moment (even if you checked previously). This can be due to the browser running out of storage space for the domain, or because the browser is running in privacy mode or with an ad blocker extensions or something. Also, your new RegExp() calls should be lifted outside of the for loops, so that they aren't continuously recreated. For wpTextbox1.value, realise that sometimes the content might be managed by an editor (The syntaxhighlighting beta does this for instance). We use the jquery.textSelection plugin to abstract way from these differences. Don't check document.title, check mw.config.get( 'wgTitle' ) or mw.config.get( 'wgPageName' ). And when you use mw.util.addPortlink, you have to ensure that the mediawiki.util plugin is loaded already, which you can do by using mw.loader.using. —TheDJ (talk • contribs) 14:47, 27 October 2017 (UTC)

Rough rough talk through

Script dependencies

(Copy of Wikipedia:Village pump (technical)#Script dependencies)

Let's say a script works for one person, but not another. Or it's working on two machines, but after one is cold booted, it doesn't work on that one.

How would one find the dependencies required by the script?   The Transhumanist 12:16, 12 February 2017 (UTC)

@The Transhumanist: I am guessing that your problems are not caused by a lack of dependencies, but rather by the way you are using the localStorage object. According to the docs, you should be using localStorage.setItem('foo', 'bar'), not localStorage.foo = 'bar'. If you use the API in a non-standard way I wouldn't be surprised if there were differences between the way the various browsers handle it. — Mr. Stradivarius ♪ talk ♪ 13:18, 12 February 2017 (UTC)
Actually, after some more reading, it seems that the localStorage.foo = 'bar' syntax is fine (although the setItem syntax is preferred). That link does give some other suggestions as to things that could be wrong, though - localStorage might not be implemented on old browsers, it might be disabled by users, or it might be full. — Mr. Stradivarius ♪ talk ♪ 15:01, 12 February 2017 (UTC)
Also, I would use a unique prefix for your localStorage keys, maybe olutils_ (so the current key would be olutils_redlinks), to reduce the chance of clashes between your data and other localStorage data saved by MediaWiki or by other gadgets. — Mr. Stradivarius ♪ talk ♪ 13:24, 12 February 2017 (UTC)
@Mr. Stradivarius: It had little to do with memory, but your suggestion provided the essential clue. Since I had 2 versions of the script running simultaneously, the second one worked because of data stored locally by the first one. Without that storage there, the second script failed, which became apparent when I customized the localstorage key per your suggestion. Which led me to a bug. I fixed the bug, and the now the second script works on its own. Though there are still some bugs (the menu item has to be clicked again after getting a preview, twice, for it to work, but it does work). Thank you! The Transhumanist 00:59, 13 February 2017 (UTC)
@The Transhumanist: Also, all calls to LocalStorage should always be wrapped in a try catch. Localstorage can easily fail due to being full, or due to being in a privacy mode or some other restriction that the browser is placing. —TheDJ (talk • contribs) 07:32, 13 February 2017 (UTC)
Thanks, I'll look that up. The Transhumanist 20:01, 13 February 2017 (UTC)

 ------------------ End of copy ----------------

Discussions

Related Articles

Timelines

Top Qs

Fact Checks