User:Inductiveload/index preview.js

// // Tool to helpfully preview page in an index (good for checking page numbers) //

(function($, mw) { "use strict";

function install_css(css) { $('head').append(`${css} `); }

var QUALITY = { WITHOUT_TEXT: 0, NOT_PROOFREAD: 1, PROBLEMATIC: 2, PROOFREAD: 3, VALIDATED: 4, };

var Preview = { strings: { show_grid: 'Show page grid', show_grid_tooltip: 'Show a grid of all page images', hide_grid: 'Hide page grid', hide_grid_tooltip: 'Hide the grid of all page images', mark_without_text: 'Without text', without_text_summary: '/* Without text */', problematic_summary: "/* Problematic */", created_ok_msg: 'Page created successfully', created_fail_msg: 'Page could not be created', mark_raw_image: 'Raw img', mark_raw_image_tooltip: 'Mark this page problematic, with the template (for full-page images).', raw_image_summary: "Template:raw image", mark_without_text_tooltip: 'Mark this page as "without text" (for blank pages).', mark_table: 'Table', mark_table_tooltip: 'Mark this page problematic, with the template (for full-page tables).', table_summary: 'Template:Missing table', tool_name: "Index Preview", summary_note: "using $2",

},   tool_link: "User:Inductiveload/index preview", tag_name: "index preview tool", toast_timeout: 4000, min_retry_timeout: 200, max_retry_timeout: 2000, popup_padding: 5, popup_img_size: 350, grid_img_size: 100, grid_gap: 6, thumb_rate_limit: 70 / 30, thumb_rate_margin: 1.5, access_key: 'G', };

var window_manager;

/**  * Get the imageinfo for a given page and callback with it   * @param  {[type]} img    the file name (without NS) * @param {[type]} page   the page number * @param {[type]} size   the requested size * @param {[type]} iiprop the requested properties, |-separated * @param {[type]} ii_cb  the function to call with the imageinfo */ function get_imageinfo_for_page(img, page, size, iiprop, ii_cb) {

var api = new mw.Api;

api.get({       'action': 'query',        'prop': 'imageinfo',        'titles': "File:" + img,        'formatversion': 2,        'format': 'json',        'iiprop': iiprop,        'iiurlparam': "page" + page + "-" + size + "px"      }) .done(function(data) {       ii_cb(data.query.pages[0].imageinfo[0]);      }); }

/*  * Do an action if a specific page exists, or not */ function if_page_exists(page, is_exists, pg_cb) {

var api = new mw.Api; api.get({       'action': 'query',        'prop': 'pageprops',        'titles': page,        'formatversion': 2,        'format': 'json',      }) .done(function(data) {       var page = data.query.pages[0];        if ((is_exists && page.missing === undefined) || (!is_exists && page.missing === true)) {         pg_cb(page);        }      }); }

function update_labels(add) {

var text, tt; if (add) { text = Preview.strings.show_grid; tt = Preview.strings.show_grid_tooltip; } else { text = Preview.strings.hide_grid; tt = Preview.strings.hide_grid_tooltip; }

$(".userjs-prp-index-grid-trigger a") .text(text) .attr("title", tt); }

function activate_grid {

if ($(".userjs-prp-index-grid").length > 0) { $(".userjs-prp-index-grid").remove; update_labels(true); } else { var filename = mw.config.get("wgTitle");

// The reason we do this, rather than scraping the pagelist is that // the page list isn't guaranteed to have every single link in it     // TODO: fall back to pagelist for non DJVU/PDF files get_imageinfo_for_page(filename, 1, 100, "dimensions|url", function(imageinfo) {       build_grid(filename, imageinfo.thumburl, imageinfo.pagecount);        update_labels(false);      }); } }

function strip_ns(title) { return title.replace(/^[A-Za-z]+:/, ""); }

/*  * Get the file and page number from a link */ function get_file_from_pagelink($link) { const url = new URL($link.attr('href'), "https://" + mw.config.get("wgServer"));

var splt = url.pathname.split("/");

var parts = null;

if (splt[1] === "wiki") { parts = [ strip_ns(splt[2]), splt[3] ]; } else if (splt[1] === "w") { // redlink? var title = url.searchParams.get("title").split("/"); parts = [ strip_ns(title[0]), title[1]]; } else { console.error("Unknown URL structure: ", url); }

return parts; }

function get_random_integer(min, max) { return Math.floor(Math.random * (max - min + 1) ) + min; }

/*  * Handler for clicks on page thumbnails * Alt: show a popup and queue an zoomed image load into it  * * $attachment_pt: where the popup attaches * url_getter: function that find the url calls the given callback with that url */ function page_thumb_click(event, page_title, $attachment_pt, url_getter) { if (!event.altKey) { return; }

var progbar = new OO.ui.ProgressBarWidget({     progress: false,    });

var popup = new OO.ui.PopupWidget({     $floatableContainer: $attachment_pt,      autoClose: true,      align: 'center',      classes: ["userjs-prp-page-preview-popup"],      hideWhenOutOfView: false,      data: {        page_title: page_title,        progbar: progbar,      }    });

popup.setAnchorEdge("bottom");

$(' ')     .addClass('userjs-prp-page-popup-controls') .appendTo(popup.$body);

$(' ')     .addClass('userjs-prp-page-popup-image') .append(progbar.$element) .appendTo(popup.$body);

// Append and display $(document.body).append(popup.$element); popup.toggle(true);

url_getter(function(img_url) {     load_img_into_popup(img_url, popup);    });

if_page_exists(page_title, false, function {     load_quick_tools_into_popup(popup);    }); }

function load_img_into_popup(img_url, popup) {

var img = $(" ") .attr("src", img_url) .on('error', handle_load_error) .on("load", function {

popup.getData.progbar.toggle(false);

popup.$body .find(".userjs-prp-page-popup-image") .append(img);

var marg = popup.$body.outerWidth(true) - popup.$body.innerWidth + Preview.popup_padding * 2; popup.setSize(img.width + marg, null, true); }   );  }

function page_tool_button(text, title, onclick) { var button = new OO.ui.ButtonWidget( {       label: text,        title: title,        flags: 'progressive'    });

button.on('click', onclick); return button; }

function load_quick_tools_into_popup(popup) {

var wot_button = page_tool_button(     Preview.strings.mark_without_text,      Preview.strings.mark_without_text_tooltip,      function {        mark_without_text(popup.getData.page_title);      }    );

var raw_button = page_tool_button(     Preview.strings.mark_raw_image,      Preview.strings.mark_raw_image_tooltip,      function {        mark_raw_image(popup.getData.page_title);      }    );

var table_button = page_tool_button(     Preview.strings.mark_table,      Preview.strings.mark_table_tooltip,      function {        mark_table(popup.getData.page_title);      }    );

var btn_grp = new OO.ui.ButtonGroupWidget({       items: [wot_button, raw_button, table_button]    });

popup.$body.find(".userjs-prp-page-popup-controls") .append(btn_grp.$element); }

function construct_page_content(header, body, footer, quality) { return ' ' + header + ' ' + body + ' ' + footer + ' '; }

function set_page_quality(pg_title, quality) {

pg_title = pg_title.replace(/_/g, ' ');

$('a[title="' + pg_title + '"], a[title^="' + pg_title + ' "], '+       '.userjs-prp-index-grid-page[data-pg_title="' + pg_title + '"] .userjs-prp-index-grid-pageindex') .addClass("quality" + quality) .removeClass("new") }

function create_page(pg_title, summary, header, body, footer, quality) { var content = construct_page_content(header, body, footer, quality);

summary += "; " + Preview.strings.summary_note .replace("$1", Preview.tool_link) .replace("$2", Preview.strings.tool_name);

var api = new mw.Api; api .create(pg_title,       {          summary: summary,          tags: Preview.tag_name        },        content      ) .done(function {       show_toast('success', Preview.strings.created_ok_msg, pg_title);        set_page_quality(pg_title, quality);      }) .fail(function(error, info) {       show_message(Preview.strings.created_fail_msg, info.error.info + "\n" + pg_title);     }); }

function show_message(title, msg, button_text) { var message_dialog = new OO.ui.MessageDialog; window_manager.addWindows([message_dialog]);

window_manager.openWindow(message_dialog, {     title: title,      message: msg,      actions: [        {          action: 'accept',          label: button_text || "OK",          flags: 'primary'        }      ]    }); }

function show_toast(type, title, message) {

var bubble_type = type || "info";

mw.notify(message, {     title: title,      type: bubble_type,      autoHideSeconds: Preview.toast_timeout    }); }

/*  * Mark a given page as a "raw image" page */ function mark_raw_image(pg_title) { var summary = Preview.strings.problematic_summary + " " + Preview.strings.raw_image_summary; create_page(pg_title, summary,     , , '', QUALITY.PROBLEMATIC); }

/*  * Mark a given page as a "raw image" page */ function mark_table(pg_title) { var summary = Preview.strings.problematic_summary + " " + Preview.strings.table_summary; create_page(pg_title, summary,     , , '', QUALITY.PROBLEMATIC); }

/*  * Mark a given page as "without text" */ function mark_without_text(pg_title) { create_page(pg_title, Preview.strings.without_text_summary,     , , '', QUALITY.WITHOUT_TEXT); }

var reload_queue = []; var reload_timer = null;

function check_reload_queue { // slightly under the max rate limit once we hit the limiter var timer_interval = Preview.thumb_rate_margin * (1000 / Preview.thumb_rate_limit);

if (reload_timer === null && reload_queue.length > 0) { reload_timer = setInterval(function {       var to_reload = reload_queue[0];        reload_queue.shift;

if (reload_queue.length === 0) { window,clearInterval(reload_timer); reload_timer = null; }

to_reload.attr("src", to_reload.attr("src")); }, timer_interval);   }  }

/*  * If an image fails to load (probably because of a rate limit), ask again * in a short while. */ function handle_load_error(event) { var $img = $(event.target); // console.log("Load error", event);

reload_queue.push($img); // we really want the first images to load first reload_queue.sort(function(a, b) {     return a.data('pg_num') - b.data('pg_num');    });

check_reload_queue; }

/*  * Copies prooreading status pagenum and href from an index-pagelist link */ function assign_page_status_from_link($pg_div, $link) { var classes = ["quality0", "quality1", "quality2", "quality3", "quality4", "new"];

var $targets = $pg_div.find(".userjs-prp-index-grid-pageindex");

for (var i = 0; i < classes.length; ++i) { if ($link.hasClass(classes[i])) { $targets.addClass(classes[i]); }   }

// chop off the hidden 00's   var children = $link[0].childNodes; var text = children[children.length - 1].nodeValue;

$targets .append(" — " + text) .attr("href", $link.attr("href"));

// and assign to the image link $pg_div.find(".userjs-prp-index-grid-page a") .attr("href", $link.attr("href")); }

function assign_page_status($pg_div) {

var pg_num = $pg_div.data("pg_num");

// first, see if we can sneak it out of the pagelist

// look for existing pages with quality classes var selector = ".index-pagelist a[href$='/" + pg_num + "']"; $(selector).each(function(idx, elem) {     assign_page_status_from_link($pg_div, $(elem));    });

// and now for red links selector = ".index-pagelist a[href*='/" + pg_num + "&action']"; $(selector).each(function(idx, elem) {     assign_page_status_from_link($pg_div, $(elem));    }); }

function build_grid(filename, url, num_pages) {

$(".userjs-prp-index-grid").parents("tr").first.remove;

var $tr = $(" "); var $grid = $(" ") .addClass("userjs-prp-index-grid") .appendTo($tr);

var $tbody = $(".index-pagelist").parents("tr").first.parent;

$tbody.append($tr);

var last_slash = url.lastIndexOf("/"); var page_prefix = mw.config.get("wgServer") + mw.config.get("wgArticlePath").replace("$1", "Page:" + filename);

for (var page = 1; page <= num_pages; ++page) { var page_url = url.replace("/page1-", "/page" + page + "-");

var $page_div = $(" ") .addClass("userjs-prp-index-grid-page") .attr( { 'data-pg_num': page } ) .attr( { 'data-pg_title': "Page:" + filename + "/" + page } ) .data('pg_num', page);

var $num = $(" ") .addClass("userjs-prp-index-grid-info") .append($("")         .addClass("userjs-prp-index-grid-pageindex")          .append("#" + page)) .appendTo($page_div);

var $a = $("") .addClass("popups_nopopup") // disable popups here .attr("href", page_prefix + "/" + page) .appendTo($page_div);

var $img = $(" ") .attr("src", page_url) .on('error', handle_load_error) .data('pg_num', page) .click(function(event) {

var src = $(this).attr("src");

var url_getter = function(cb) { var img_url = src.replace(/\d+px/, Preview.popup_img_size + "px"); cb(img_url); };

// no ES-6 let, so cheat and use a data attribute var page_title = "Page:" + filename + "/" + $(this).data('pg_num');

page_thumb_click(event,           page_title,            $(event.target).parents(".userjs-prp-index-grid-page").first,            url_getter); })       .appendTo($a);

assign_page_status($page_div);

$grid.append($page_div); } }

/*  * A pagelist item was clicked - look up the image URL and pop-up the image */ function on_pagelist_click(event) { var $link_elem = $(event.target); var img_page = get_file_from_pagelink($link_elem); var size = Preview.popup_img_size; var page_name = "Page:" + img_page[0] + "/" + img_page[1];

get_imageinfo_for_page(img_page[0], img_page[1], size, "url", function(imageinfo) {     var url_getter = function(url_cb) {        url_cb(imageinfo.thumburl);      };

page_thumb_click(event, page_name, $link_elem, url_getter); }); }

function add_portlet(cb) { var portlet = mw.util.addPortletLink(     'p-tb',      '#',      ,      't-show-grid',      ,      Preview.access_key || undefined,    );

$(portlet) .addClass("userjs-prp-index-grid-trigger") .click(function(e) {       e.preventDefault;        cb;      });

update_labels(true); }

/*  * Add a button at the top right of the pagelist * This is enWS specific and depends on the layout of  * MediaWiki:Proofreadpage index template */ function add_page_list_button($btn) { $btn.css({float: "right"}); $(".index-pagelist").parents("td").first.find("> :first-child").prepend($btn); }

function add_grid_button(cb) {

var $link = $("") .click(cb);

var $span = $(" ") .addClass("userjs-prp-index-grid-trigger") .append("[", $link, "]");

add_page_list_button($span); update_labels(true); }

function init_index_grid_gadget {

window_manager = new OO.ui.WindowManager; $('body').append(window_manager.$element);

// console.log("Init Index Page Grid"); var max_w = Preview.grid_img_size * 10 + Preview.grid_gap * 10;

// Install CSS install_css('\ .userjs-prp-index-grid {\ display: grid;\  grid-gap: ' + Preview.grid_gap + 'px;\  grid-template-columns: repeat(auto-fill, minmax(' + Preview.grid_img_size + 'px, 1fr));\  max-width: ' + max_w + 'px;\ }\ \ .userjs-prp-index-grid-page {\  text-align: center;\  position: relative;\  margin-bottom: 5px;\ }\ .userjs-prp-index-grid-info {\  width:100%;\ }\ .userjs-prp-page-popup-image img,\ .userjs-prp-index-grid-page img {\  max-width: 100%;\  height: auto;\  border: 1px solid lightgrey;\ }\ .userjs-prp-page-preview-popup .oo-ui-popupWidget-popup {\  padding: ' + Preview.popup_padding + 'px;\ }\ .userjs-prp-page-popup-image {\  margin-top: 5px;\ }\ .userjs-prp-page-popup-controls {\  text-align: center;\  font-size: 0.8rem;\ }\ '); // install the pagelist previewer always $(".index-pagelist a") .click(on_pagelist_click);

add_portlet(activate_grid); add_grid_button(activate_grid); }

mw.hook( 'ext.gadget.index-preview-grid.config' ).fire( Preview );

if (mw.config.get("wgCanonicalNamespace") === "Index") { mw.loader.using(['mediawiki.util', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows'], function {     init_index_grid_gadget;    }); }

}(jQuery, mediaWiki));