User:N8wilson/EggHunt.js

$.when( mw.loader.using( 'mediawiki.util' ), $.ready ).then( function {  // Cease and desist when outside of article space  if ( mw.config.get( 'wgCanonicalNamespace' ) !== '') return;  mcss = 3;        // minimum common substring to count  numEggs = 0;     // will exclude "hidden" category  marked = false;  // flag to keep from double-marking  // Recursively find total length of *ordered* common sequences using  // a greedy approach that always selects the longest unused sequence first  //  // xs -> x_start, xe -> e_end, x_i -> x_iterator, mx -> (position of) max_x  // same pattern with y vars  var inCommon = function(mat, xs, xe, ys, ye) {    if (xe - xs < mcss || ye - ys < mcss)  return 0;    // set up contextual vars (to avoid clobbering)    var max = mat[xs][ys], mx = xs, my = ys;    for (xi = xs; xi < xe; xi++) {      for (yi = ys; yi < ye; yi++) {        if (mat[xi][yi] > max) { max = mat[xi][yi]; mx = xi; my = yi; }     }    }    return (max < mcss) ? 0 : max + inCommon(mat, xs, mx-max+1, ys, my-max+1) + inCommon(mat, mx+1, xe, my+1, ye); }; // Score a link using longest common substring as a percent of the length of the shortest string var strScore = function(short, long) { if (typeof(short) != "string" || typeof(long) != "string") return 0; shortA = new Array(short.length); for (s=0; s<short.length; s++) { shortA[s] = new Array(long.length); shortA[s][0] = short[s] == long[0] ? 1 : 0;   }    for (l=0; l<long.length; l++) { shortA[0][l] = short[0] == long[l] ? 1 : 0;   }    for (s=1; s<short.length; s++) { for (l=1; l<long.length; l++) { if (short[s] == long[l]) { shortA[s][l] = 1 + shortA[s-1][l-1]; } else { shortA[s][l] = 0; }     }    }    sharedSeqs = inCommon(shortA, 0, shortA.length, 0, shortA[0].length); return sharedSeqs / Math.min(short.length, long.length); //return shortA.flat.reduce(function(a,b){return Math.max(a,b);}, 0) / Math.min(short.length, long.length); }; // Categories and scoring var egg_cats = [ {min: 0.85, mark:'', cnt: 0, name:'hidden'}, {min: 0.40, mark:'🥚', cnt: 0, name:'unlikely'}, {min: 0.20, mark:'🐣', cnt: 0, name:'possible'}, {min: 0.02, mark:'🐥', cnt: 0, name:'probable'}, {min: 0.00, mark:'🐤', cnt: 0, name:'unmatched'}, ]; // Filter level 1: links in paragraph tags of the article content $("#mw-content-text p a").filter(   function(idx, el) {      // Filter level 2: links must have title attribute, visible text, and a target beginning with /wiki/....      // (mostly so we can use these assumptions later)      return $(this).attr("title") && $(this).text && (!$(this).attr("href").indexOf("/wiki/"));    }  ).filter(    function(idx, el) {      // Filter level 3: Remove inline timeplates      return $(this).parents(".Inline-Template").length == 0;    }  ).each(    function(idx, el) {      // build a lower case text and a title with any disambig clarifiers removed (trailing parens)      loc = $(this).attr("title").search(/[ _]\(.+\)$/);      title_lc = (loc >= 0) ? $(this).attr("title").substr(0,loc).toLowerCase : $(this).attr("title").toLowerCase;      text_lc = $(this).text.toLowerCase;      // short-circuit if either the title or link text is fully contained in the other (not EGG) if (text_lc.indexOf(title_lc) >= 0 || title_lc.indexOf(text_lc) >= 0 ) return 1; // otherwise report possible EGG score = strScore(text_lc, title_lc); for (c = 0; c= egg_cats[c].min) { egg_cats[c].cnt++; $(this).addClass("eggHunt-"+egg_cats[c].name); break; }     }      console.log('[' + score.toFixed(3) + '], "' + $(this).text + '", "' + $(this).attr("title") + '"'); numEggs++; } );  // remove count of hidden eggs  numEggs -= egg_cats[0].cnt;   // Install UI hook  var node = mw.util.addPortletLink('p-cactions', "#", numEggs + ' possible 🥚s', 'ca-egghunt', 'Tag '+numEggs+' possible EASTEREGGs in article');  $(node).on('click', function(e) { if (!marked) { for (c=0; c<egg_cats.length; c++) { $("a.eggHunt-"+egg_cats[c].name).after(egg_cats[c].mark); }     marked = true; }   return false; }); });