Jump to content

User:DVRTed/sandbox/AjaxLoader.js

From Wikipedia, the free encyclopedia
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.
// This is a fork of [[User:NguoiDungKhongDinhDanh/AjaxLoader.js]] with an added option to refresh the current page
/* jshint esversion: 10, maxerr: 9999, unused: true, quotmark: single */

$(() => {
  const spn = (mw.config.get("wgCanonicalSpecialPageName") || "").toLowerCase();
  const lc = (str) => str.toLowerCase();
  const switches = function (cases, def) {
    for (const i in cases) {
      if (i.toLowerCase() === spn) {
        return cases[i];
      }
    }
    return def;
  };

  // Main functions
  const linktoggle = function () {
    window.ajaxloaderloading = !window.ajaxloaderloading;
  };
  const spinner = function () {
    const $l = $("<div>").attr("class", "ajaxloader-loader");

    for (let i = 0; i < 12; i++) {
      $l.append(
        $("<div>")
          .attr("class", "ajaxloader-loader-items")
          .css({
            animation: "ajaxloader-loader 1.2s linear infinite",
            transform: `rotate(${i * 30}deg)`,
            "animation-delay": Math.round((-1.1 + i * 0.1) * 10) / 10 + "s",
          })
      );
    }

    return $("<div>")
      .attr({
        class: "ajaxloader-loader-wrapper",
        id: "ajaxloader-loader",
      })
      .append($l);
  };

  const createRefreshButton = function () {
    return $("<button>")
      .attr({
        id: "ajaxloader-refresh-btn",
        class: "ajaxloader-refresh-button",
        title: "Refresh current page content (Alt+R)",
      })
      .html("↻")
      .css({
        position: "fixed",
        top: "10px",
        right: "10px",
        zIndex: "9999",
        padding: "8px 12px",
        backgroundColor: "#0645ad",
        color: "white",
        border: "none",
        borderRadius: "4px",
        cursor: "pointer",
        fontSize: "16px",
        fontWeight: "bold",
      })
      .hover(
        function () {
          $(this).css("backgroundColor", "#0b0080");
        },
        function () {
          $(this).css("backgroundColor", "#0645ad");
        }
      );
  };

  const init = function (object) {
    const {
      links,
      linkswrapper,
      linkextend,
      main,
      parent,
      preload,
      preupdate,
    } = object;
    const updateWrapper =
      object.update !== "unwrapped"
        ? (linkswrapper || links) &&
          function (content) {
            if (preupdate) {
              preupdate();
            }
            $(linkswrapper || links).each(function (index) {
              $(this).replaceWith($(linkswrapper || links, content)[index]);
            });
          }
        : function (content) {
            let $links = [
              $(parent || "#mw-content-text").clone(),
              $(parent || "#mw-content-text", content).clone(),
            ];

            $links = $links.map(($el) => {
              $el.find("*").not("a").remove();
              $el.html(
                $el
                  .html()
                  .trim()
                  .replace(/\s*title=".*?(?<!\\)"(\s*)/g, "$1")
                  .replace(/.*?(.+)\s*\1.*?/, "$1")
              );
              return $el;
            });

            $(links).each(function () {
              $(this).prop(
                "outerHTML",
                $(this)
                  .prop("outerHTML")
                  .replace(/\s*title=".*?"(\s*)/g, "$1")
              );
            });

            for (const _ of [0, 1]) {
              $("#mw-content-text").html((_, oldHtml) =>
                oldHtml.replace($links[0].html(), $links[1].html())
              );
            }
          };

    if (!links) {
      console.warn("No links.");
      return;
    }

    const update = function (response) {
      updateWrapper(response);
      $(main).replaceWith($(main, response));
      if (preload) {
        preload();
      }
      mw.hook("wikipage.content").fire($(main));
    };

    const initialHtml = document.documentElement.outerHTML;
    // handle navigating between history items pushed by this script
    window.addEventListener("popstate", (event) => {
      update(event?.state?.response ?? initialHtml);
    });

    const doRefresh = function () {
      if (window.ajaxloaderloading) {
        return;
      }

      linktoggle();
      const $spinner = spinner();
      $(main).hide().after($spinner);

      const currentUrl = new URL(window.location.href);
      if (linkextend) {
        Object.entries(linkextend).forEach(([key, value]) => {
          currentUrl.searchParams.set(key, value);
        });
      }

      $.ajax({
        url: currentUrl.toString(),
        success: function (response) {
          $spinner.remove();
          linktoggle();
          update(response);
          mw.notify("Page refreshed successfully!", {
            title: "AjaxLoader",
            type: "success",
          });
        },
        error: function () {
          $spinner.remove();
          linktoggle();
          $(main).show();
          mw.notify("Cannot refresh page.", {
            title: "AjaxLoader",
            type: "warn",
          });
        },
      });
    };

    const $refreshBtn = createRefreshButton();
    $("body").append($refreshBtn);
    $refreshBtn.on("click", doRefresh);

    $(document).on("keydown", function (e) {
      if (e.altKey && e.key.toLowerCase() === "r") {
        e.preventDefault();
        doRefresh();
      }
    });

    $("body").on("click", links, function (e) {
      e.preventDefault();
      if (window.ajaxloaderloading) {
        return;
      }

      linktoggle();
      const $spinner = spinner();
      $(main).hide().after($spinner);

      const link = new URL($(this).prop("href"));
      if (linkextend) {
        Object.entries(linkextend).forEach(([key, value]) => {
          link.searchParams.set(key, value);
        });
      }

      $.ajax({
        url: link.toString(),
        success: function (response) {
          $spinner.remove();
          linktoggle();
          update(response);
          window.history.pushState({ response: response }, "", link); // add new URL to history/urlbar
        },
        error: function () {
          $spinner.remove();
          linktoggle();
          $(main).show();
          mw.notify("Cannot get pages.", { title: "AjaxLoader", type: "warn" });
        },
      });
    });
  };
  const includes = function (array, element = spn) {
    array = array instanceof Array ? array : [array];
    return array.some((e) => e.toLowerCase() === element.toLowerCase());
  };
  const join = function (links, parent, separator) {
    const s = [];
    for (const l of links) {
      s.push(`${parent}${separator || " > a"}${l}`);
    }
    return s.join(", ");
  };
  const load = function (server, title) {
    mw.loader.load(
      `//${server}/w/index.php?title=${title}&action=raw&ctype=text/javascript`
    );
  };

  // CSS
  // For attribution: //loading.io/css (CC0)
  const css = `
		.ajaxloader-loader-wrapper {
			display: block;
			margin: 3em 0;
			text-align: center;
		}
		.ajaxloader-loader {
			display: inline-block;
			position: relative;
			width: 80px;
			height: 80px;
		}
		.ajaxloader-loader .ajaxloader-loader-items {
			transform-origin: 40px 40px;
		}
		.ajaxloader-loader .ajaxloader-loader-items::after {
			content: ' ';
			display: block;
			position: absolute;
			top: 4px;
			left: 36px;
			width: 6px;
			height: 18px;
			border-radius: 20%;
			background: #000000;
		}
		@keyframes ajaxloader-loader {
			0% {
				opacity: 1;
			}
			100% {
				opacity: 0;
			}
		}
		.ajaxloader-refresh-button {
			transition: background-color 0.2s ease;
		}
		.ajaxloader-refresh-button:active {
			transform: scale(0.95);
		}
	`;
  mw.loader.addStyleTag(css, document.head.children[0]); // easily overridden

  // Data
  const data = [
    {
      name: "Navlinks inside mw-pager-navigation-bar ([[:phab:T308364]])",
      check: function () {
        return (
          document.getElementsByClassName("mw-pager-navigation-bar").length > 0
        );
      },
      links: join(
        [
          ".mw-lastlink",
          ".mw-firstlink",
          ".mw-prevlink",
          ".mw-nextlink",
          ".mw-numlink",
        ],
        ".mw-pager-navigation-bar"
      ),
      linkswrapper: ".mw-pager-navigation-bar",
      main: $(".mw-pager-body").length
        ? ".mw-pager-body"
        : switches(
            {
              Log: ".mw-logevent-loglines",
              "": "#pagehistory",
              AbuseLog: "#mw-content-text > form",
              Search: ".mw-search-results-container",
              WhatLinksHere: "#mw-whatlinkshere-list",
              GlobalUsage: "#mw-globalusage-result",
            },
            "#mw-content-text ul, #mw-content-text ol"
          ),
      preload: function () {
        if (includes("NewImages")) {
          $("#mw-content-text > ul").before(
            $(".mw-pager-navigation-bar").clone()
          );
        }
      },
      preupdate: function () {
        if (includes("NewImages")) {
          $($(".mw-pager-navigation-bar")[0]).remove();
        }
      },
    },
    {
      name: "Special pages with -nav classes",
      check: function () {
        return includes(["AllPages", "PrefixIndex"]);
      },
      links: ".mw-" + spn + "-nav > a",
      linkswrapper: ".mw-" + spn + "-nav",
      main: ".mw-" + spn + "-body",
    },
    {
      name: "Abuse filter history",
      check: function () {
        return (
          spn === lc("AbuseFilter") &&
          $(".mw-abusefilter-history-buttons").length > 0
        );
      },
      links: ".mw-abusefilter-history-buttons a.oo-ui-buttonElement-button",
      linkswrapper: ".mw-abusefilter-history-buttons",
      main: "#mw-content-text > table.wikitable",
      preload: function () {
        $("#mw-content-text > table.wikitable").before(
          $(".mw-abusefilter-history-buttons").clone()
        );
      },
      preupdate: function () {
        $($(".mw-abusefilter-history-buttons")[0]).remove();
      },
    },
    {
      name: "Modernized special pages with button-like navlinks",
      check: function () {
        return includes([
          "AllMessages",
          "ProtectedPages",
          "BlockList",
          "AutoblockList",
          "AbuseFilter",
          "ListFiles",
        ]);
      },
      links: ".TablePager_nav a",
      linkswrapper: ".TablePager_nav",
      main: switches({
        AllMessages: "#mw-allmessagestable",
        ProtectedPages: ".mw-protectedpages",
        BlockList: ".mw-blocklist",
        AutoblockList: ".mw-blocklist",
        AbuseFilter: ".mw-datatable",
        ListFiles: ".mw-datatable",
      }),
    },
    {
      name: "Contributions",
      check: function () {
        return includes(["Contributions"]);
      },
      links: ".mw-pager-navigation-bar > a",
      linkswrapper: ".mw-pager-navigation-bar",
      main: ".mw-pager-body",
    },
    {
      name: "SearchTranslations",
      check: function () {
        return includes(["SearchTranslations"]);
      },
      links: ".tux-pagination-links > a",
      linkswrapper: ".tux-pagination-links",
      main: ".results",
    },
    {
      name: "Categories",
      check: function () {
        return mw.config.get("wgNamespaceNumber") === 14;
      },
      exempt: function () {
        load("en.wikipedia.org", "User:NguoiDungKhongDinhDanh/AjaxCat.js");
      },
    },
    {
      name: "Translatable pages",
      check: function () {
        return $("ul.mw-pt-languages-list").length;
      },
      links: ".mw-pt-languages-list a.mw-pt-progress",
      linkswrapper: ".mw-pt-languages-list",
      main: "#bodyContent",
    },
    {
      name: "Files",
      check: function () {
        return mw.config.get("wgNamespaceNumber") === 6;
      },
      links: join(
        [
          ".mw-lastlink",
          ".mw-firstlink",
          ".mw-prevlink",
          ".mw-nextlink",
          ".mw-numlink",
        ],
        ".mw-pager-navigation-bar"
      ),
      linkswrapper: ".mw-pager-navigation-bar",
      main: ".filehistory",
    },
  ];

  // Init.
  for (const i of data) {
    if (i.check()) {
      console.log(i.name);
      if (!i.exempt) {
        if (i.preload) {
          i.preload();
        }
        init(i);
      } else {
        i.exempt();
      }
      break;
    }
  }
});