Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Join the Playtest on Steam Now: SpiritVale

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
Tags: Mobile edit Mobile web edit
No edit summary
Tags: Reverted Mobile edit Mobile web edit
Line 1,576: Line 1,576:
(function () {
(function () {
   /* ================================================================== */
   /* ================================================================== */
   /* MODULE: Universal Popups (v2)                                     */
   /* MODULE: Universal Popups (v2) — refined                            */
   /*  - Popup B aesthetic everywhere                                  */
   /*  - Removes redundant hint for click-open (large) popups            */
   /*  - Small = hover/focus (non-click open on hover-capable devices)   */
   /*  - Leaves hover hints optional (but CSS currently hides legacy)   */
   /*  - Large = click/tap open, header click closes, outside closes    */
   /*  - Keeps links inside popups fully interactive (Popups friendly)   */
  /*   - Supersedes legacy injected black tips by intercepting on window */
   /* ================================================================== */
   /* ================================================================== */


Line 1,586: Line 1,585:
   var COMMON = (SV.common = SV.common || {});
   var COMMON = (SV.common = SV.common || {});


   var POPV2_VERSION = 1;
   var POPV2_VERSION = 2; // bumped due to header/hint changes
   if (typeof COMMON.popupsV2Init === "number" && COMMON.popupsV2Init >= POPV2_VERSION) return;
   if (typeof COMMON.popupsV2Init === "number" && COMMON.popupsV2Init >= POPV2_VERSION) return;
   COMMON.popupsV2Init = POPV2_VERSION;
   COMMON.popupsV2Init = POPV2_VERSION;


   try {
   try { document.documentElement.classList.add("sv-uipop-v2"); } catch (e) {}
    document.documentElement.classList.add("sv-uipop-v2");
  } catch (e) {}


   var DEF_SEL = ".sv-def";
   var DEF_SEL = ".sv-def";
Line 1,607: Line 1,604:
   var HOVER_CAPABLE = false;
   var HOVER_CAPABLE = false;
   try {
   try {
     HOVER_CAPABLE =
     HOVER_CAPABLE = !!window.matchMedia && window.matchMedia("(hover:hover) and (pointer:fine)").matches;
      !!window.matchMedia &&
      window.matchMedia("(hover:hover) and (pointer:fine)").matches;
   } catch (e) {
   } catch (e) {
     HOVER_CAPABLE = false;
     HOVER_CAPABLE = false;
Line 1,619: Line 1,614:
     open: false,
     open: false,
     pinned: false,
     pinned: false,
     mode: "hover", // hover | click
     mode: "hover",     // hover | click
     size: "sm",   // sm | lg
     size: "sm",         // sm | lg
     trigger: null,
     trigger: null,
     lastPointerType: "mouse",
     lastPointerType: "mouse",
Line 1,687: Line 1,682:
     if (t == null) return true;
     if (t == null) return true;


     var compact = String(t)
     var compact = String(t).replace(/\s+/g, "").replace(/[—–-]+/g, "").trim();
      .replace(/\s+/g, "")
      .replace(/[—–-]+/g, "")
      .trim();
 
     return !compact;
     return !compact;
   }
   }
Line 1,721: Line 1,712:
       if (!pop) return null;
       if (!pop) return null;


      // Prefer body node if present
       var body =
       var body =
         pop.querySelector(".sv-tip-pop-body") ||
         pop.querySelector(".sv-tip-pop-body") ||
Line 1,772: Line 1,762:


   function guessTitle(trigger, sourceNode) {
   function guessTitle(trigger, sourceNode) {
    // Prefer existing pop title nodes when the source is a pop container
     var pop = null;
     var pop = null;
     if (sourceNode && sourceNode.nodeType === 1) {
     if (sourceNode && sourceNode.nodeType === 1) {
Line 1,778: Line 1,767:
     }
     }
     if (pop) {
     if (pop) {
       var t =
       var t = pop.querySelector(".sv-tip-pop-title") || pop.querySelector(".sv-disclose-pop-title");
        pop.querySelector(".sv-tip-pop-title") ||
        pop.querySelector(".sv-disclose-pop-title");
       if (t && t.textContent) return String(t.textContent).trim();
       if (t && t.textContent) return String(t.textContent).trim();
     }
     }
Line 1,803: Line 1,790:
     a.textContent = "Open page";
     a.textContent = "Open page";


     if (window.mw && mw.util && typeof mw.util.getUrl === "function") {
     if (window.mw && mw.util && typeof mw.util.getUrl === "function") a.href = mw.util.getUrl(linkTitle);
      a.href = mw.util.getUrl(linkTitle);
     else a.href = String(linkTitle);
     } else {
      a.href = String(linkTitle);
    }


    // IMPORTANT: do not prevent default/capture on this link — Popups extension can hook it.
     wrap.appendChild(a);
     wrap.appendChild(a);
     return wrap;
     return wrap;
Line 1,820: Line 1,805:
     el.classList.add(opts.size === "lg" ? "sv-uipop--lg" : "sv-uipop--sm");
     el.classList.add(opts.size === "lg" ? "sv-uipop--lg" : "sv-uipop--sm");


    // Clear
     while (el.firstChild) el.removeChild(el.firstChild);
     while (el.firstChild) el.removeChild(el.firstChild);


     // Head
     // Header
     var head = document.createElement("div");
     var head = document.createElement("div");
     head.className = "sv-uipop-head" + (opts.mode === "click" ? " sv-uipop-head--clickable" : "");
     head.className = "sv-uipop-head";
 
    // Click-open popups: center title + NO hint element
    if (opts.mode === "click") head.className += " sv-uipop-head--clickable sv-uipop-head--center";


     var title = document.createElement("div");
     var title = document.createElement("div");
     title.className = "sv-uipop-title";
     title.className = "sv-uipop-title";
     title.textContent = opts.title || "Info";
     title.textContent = opts.title || "Info";
    head.appendChild(title);


     var hint = document.createElement("div");
     // Hover popups: hint is allowed (but not required)
    hint.className = "sv-uipop-hint";
    if (opts.mode !== "click" && opts.hint) {
    hint.textContent = opts.hint || "";
      var hint = document.createElement("div");
      hint.className = "sv-uipop-hint";
      hint.textContent = opts.hint;
      head.appendChild(hint);
    }


    head.appendChild(title);
    head.appendChild(hint);
     el.appendChild(head);
     el.appendChild(head);


Line 1,842: Line 1,832:
     var body = document.createElement("div");
     var body = document.createElement("div");
     body.className = "sv-uipop-body";
     body.className = "sv-uipop-body";
     if (opts.bodyPre) body.classList.add("sv-uipop-body--pre");
     if (opts.bodyPre) body.classList.add("sv-uipop-body--pre");


Line 1,849: Line 1,838:
       stripIds(clone);
       stripIds(clone);
       unhideTree(clone);
       unhideTree(clone);
      // If the clone is a list, keep it; CSS normalizes list styles inside popup
       body.appendChild(clone);
       body.appendChild(clone);
     } else if (opts.bodyText != null) {
     } else if (opts.bodyText != null) {
Line 1,856: Line 1,843:
     }
     }


     if (opts.actionsNode) {
     if (opts.actionsNode) body.appendChild(opts.actionsNode);
      body.appendChild(opts.actionsNode);
    }
 
     el.appendChild(body);
     el.appendChild(body);


     // Header close (large/click)
     // Header click closes for click-open
     if (opts.mode === "click") {
     if (opts.mode === "click") {
       head.addEventListener(
       head.addEventListener("click", function (e) {
        "click",
        e.preventDefault();
        function (e) {
        hidePopup(true);
          e.preventDefault();
      }, { once: true });
          hidePopup(true);
        },
        { once: true }
      );
     }
     }
   }
   }
Line 1,879: Line 1,859:
     if (!el || !trigger || !trigger.getBoundingClientRect) return;
     if (!el || !trigger || !trigger.getBoundingClientRect) return;


    // Reset so we can measure
     el.style.left = "0px";
     el.style.left = "0px";
     el.style.top = "0px";
     el.style.top = "0px";
    // Temporarily show for measurement
     setHidden(el, false);
     setHidden(el, false);


Line 1,900: Line 1,877:
       var h = pr.height || 180;
       var h = pr.height || 180;


      // Prefer below, left-aligned; icon triggers align right
       var isIconish = (tr.width || 0) <= 40;
       var isIconish = (tr.width || 0) <= 40;
       var left = isIconish ? (tr.right - w) : tr.left;
       var left = isIconish ? (tr.right - w) : tr.left;
       var top = tr.bottom + gap;
       var top = tr.bottom + gap;


      // Clamp horizontally
       left = clamp(left, margin, Math.max(margin, vw - w - margin));
       left = clamp(left, margin, Math.max(margin, vw - w - margin));


      // If bottom overflow, flip above
       if (top + h + margin > vh) top = tr.top - h - gap;
       if (top + h + margin > vh) {
        top = tr.top - h - gap;
      }
       top = clamp(top, margin, Math.max(margin, vh - h - margin));
       top = clamp(top, margin, Math.max(margin, vh - h - margin));


Line 1,924: Line 1,896:
   }
   }


   function hidePopup(fromUser) {
   function hidePopup() {
     if (!state.open) return;
     if (!state.open) return;


Line 1,945: Line 1,917:
     if (!trigger) return;
     if (!trigger) return;


     if (state.open && state.trigger && state.trigger !== trigger) {
     if (state.open && state.trigger && state.trigger !== trigger) hidePopup();
      hidePopup(false);
    }


     state.open = true;
     state.open = true;
Line 1,964: Line 1,934:
   function findTrigger(t) {
   function findTrigger(t) {
     if (!t) return null;
     if (!t) return null;
    // Never treat clicks/hover inside the popup as trigger changes
    if (popEl && (t === popEl || (popEl.contains && popEl.contains(t)))) return null;


     var defEl = closest(t, DEF_SEL);
     var defEl = closest(t, DEF_SEL);
Line 1,978: Line 1,951:


   function resolveOptsForTrigger(trigger) {
   function resolveOptsForTrigger(trigger) {
    // Defaults:
    // - .sv-tip-btn => small hover
    // - .sv-disclose-btn => large click
    // - .sv-def => hover if no link, click if link
     var isDef = trigger && trigger.matches && trigger.matches(DEF_SEL);
     var isDef = trigger && trigger.matches && trigger.matches(DEF_SEL);
     var isTip = trigger && trigger.matches && trigger.matches(TIP_BTN_SEL);
     var isTip = trigger && trigger.matches && trigger.matches(TIP_BTN_SEL);
Line 1,990: Line 1,958:
     var size = "sm";
     var size = "sm";


     // explicit overrides
     // Manual overrides (optional)
     var attrMode = trigger.getAttribute && trigger.getAttribute("data-sv-pop");
     var attrMode = trigger.getAttribute && trigger.getAttribute("data-sv-pop");
     if (attrMode === "hover" || attrMode === "click") mode = attrMode;
     if (attrMode === "hover" || attrMode === "click") mode = attrMode;
Line 2,006: Line 1,974:
     }
     }


     if (!attrSize) {
     if (!attrSize) size = (mode === "click") ? "lg" : "sm";
      size = mode === "click" ? "lg" : "sm";
    }
 
    var sourceNode = null;
    var title = "Info";
    var hint = "";


     if (isDef) {
     if (isDef) {
       var tip = getDefTipText(trigger);
       var tip = getDefTipText(trigger);
      var linkTitle = getDefLinkTitle(trigger);
       if (!tip) return null;
       if (!tip) return null;


       title = String(trigger.textContent || "").trim() || "Definition";
       var linkTitle = getDefLinkTitle(trigger);
      hint =
      var title = String(trigger.textContent || "").trim() || "Definition";
        mode === "click"
          ? "Click to close"
          : (HOVER_CAPABLE ? "Hover to view" : "Tap to view");


       return {
       return {
         mode: mode,
         mode: mode,
         size: size,
         size: size,
         pinned: mode === "click" ? true : (!HOVER_CAPABLE && state.lastPointerType !== "mouse"),
         pinned: (mode === "click"),
         title: title,
         title: title,
         hint: hint,
         hint: (mode === "click") ? "" : (HOVER_CAPABLE ? "Hover to view" : "Tap to view"),
         bodyText: tip,
         bodyText: tip,
         bodyPre: true,
         bodyPre: true,
         actionsNode: mode === "click" ? buildActionsForDefinition(linkTitle) : null
         actionsNode: (mode === "click") ? buildActionsForDefinition(linkTitle) : null
       };
       };
     }
     }


     sourceNode = getContentNodeFromTrigger(trigger);
     var sourceNode = getContentNodeFromTrigger(trigger);
     if (isBlankContent(sourceNode)) return null;
     if (isBlankContent(sourceNode)) return null;
    title = guessTitle(trigger, sourceNode);
    hint =
      mode === "click"
        ? "Click to close"
        : (HOVER_CAPABLE ? "Hover to view" : "Tap to view");
    // For hover-small on hover-capable devices: never pinned
    var pinned = mode === "click" ? true : (!HOVER_CAPABLE && state.lastPointerType !== "mouse");


     return {
     return {
       mode: mode,
       mode: mode,
       size: size,
       size: size,
       pinned: pinned,
       pinned: (mode === "click"),
       title: title,
       title: guessTitle(trigger, sourceNode),
       hint: hint,
       hint: (mode === "click") ? "" : (HOVER_CAPABLE ? "Hover to view" : "Tap to view"),
       bodyNode: sourceNode,
       bodyNode: sourceNode,
       bodyPre: false,
       bodyPre: false,
Line 2,067: Line 2,015:


   /* ------------------------------------------------------------------ */
   /* ------------------------------------------------------------------ */
   /* Event Intercepts (window capture) — prevents legacy handlers running */
   /* Event Intercepts (window capture) — supersedes legacy systems      */
   /* ------------------------------------------------------------------ */
   /* ------------------------------------------------------------------ */


   window.addEventListener(
   window.addEventListener("pointerdown", function (e) {
    "pointerdown",
    if (e && e.pointerType) state.lastPointerType = e.pointerType;
    function (e) {
      if (e && e.pointerType) state.lastPointerType = e.pointerType;


      var trigger = findTrigger(e.target);
    // If user clicks inside popup, allow normal interactions (links, selection)
      if (!trigger) return;
    if (isInsidePopup(e.target)) return;


      var opts = resolveOptsForTrigger(trigger);
    var trigger = findTrigger(e.target);
      if (!opts) return;
    if (!trigger) return;


      // For hover-mode on hover-capable devices, do not open on click
    var opts = resolveOptsForTrigger(trigger);
      if (opts.mode === "hover" && HOVER_CAPABLE && (e.pointerType === "mouse" || !e.pointerType)) {
    if (!opts) return;
        // Let hover handlers manage it; suppress click-open
        e.stopPropagation();
        return;
      }


      e.preventDefault();
    // Hover-mode on mouse: don’t click-open; let hover do it
    if (opts.mode === "hover" && HOVER_CAPABLE && (e.pointerType === "mouse" || !e.pointerType)) {
       e.stopPropagation();
       e.stopPropagation();
      return;
    }


      // Toggle if already open + pinned + same trigger
    e.preventDefault();
      if (state.open && state.pinned && state.trigger === trigger) {
    e.stopPropagation();
        hidePopup(true);
 
        return;
    if (state.open && state.pinned && state.trigger === trigger) {
      }
      hidePopup(true);
      return;
    }


      openPopup(trigger, opts);
    openPopup(trigger, opts);
    },
  }, true);
    true
  );


   // Hover open/close (small)
   // Hover open/close (small)
   window.addEventListener(
   window.addEventListener("mouseover", function (e) {
    "mouseover",
    if (!HOVER_CAPABLE) return;
    function (e) {
    if (isInsidePopup(e.target)) return; // allow moving into click popups without side effects
      if (!HOVER_CAPABLE) return;


      var trigger = findTrigger(e.target);
    var trigger = findTrigger(e.target);
      if (!trigger) {
    if (!trigger) {
        if (state.open && !state.pinned) hidePopup(false);
      if (state.open && !state.pinned) hidePopup(false);
        return;
      return;
      }
    }


      var opts = resolveOptsForTrigger(trigger);
    var opts = resolveOptsForTrigger(trigger);
      if (!opts || opts.mode !== "hover") return;
    if (!opts || opts.mode !== "hover") return;


      e.stopPropagation();
    e.stopPropagation();


      clearTimeout(state.hideTimer);
    clearTimeout(state.hideTimer);
      state.hideTimer = 0;
    state.hideTimer = 0;


      openPopup(trigger, opts);
    openPopup(trigger, opts);
    },
  }, true);
    true
  );


   window.addEventListener(
   window.addEventListener("mouseout", function (e) {
    "mouseout",
    if (!HOVER_CAPABLE) return;
    function (e) {
    if (!state.open || state.pinned) return;
      if (!HOVER_CAPABLE) return;
      if (!state.open || state.pinned) return;


      var trigger = findTrigger(e.target);
    var trigger = findTrigger(e.target);
      if (!trigger || trigger !== state.trigger) return;
    if (!trigger || trigger !== state.trigger) return;


      e.stopPropagation();
    e.stopPropagation();


      clearTimeout(state.hideTimer);
    clearTimeout(state.hideTimer);
      state.hideTimer = setTimeout(function () {
    state.hideTimer = setTimeout(function () {
        if (state.open && !state.pinned) hidePopup(false);
      if (state.open && !state.pinned) hidePopup(false);
      }, 60);
    }, 60);
    },
  }, true);
    true
  );


   // Click outside closes pinned
   // Outside click closes pinned
   window.addEventListener(
   window.addEventListener("click", function (e) {
    "click",
    if (!state.open) return;
    function (e) {
      if (!state.open) return;


      var trigger = findTrigger(e.target);
    // If click inside popup, do nothing (links should work; Popups can hook them)
    if (isInsidePopup(e.target)) return;


      // Clicking the trigger is handled by pointerdown toggle
    // Clicking a trigger is handled by pointerdown
      if (trigger) {
    var trigger = findTrigger(e.target);
        e.stopPropagation();
    if (trigger) {
        return;
      e.stopPropagation();
      }
      return;
    }


       // Let links inside popup work
    if (state.pinned) {
       if (isInsidePopup(e.target)) return;
       hidePopup(true);
       e.stopPropagation();
    }
  }, true);


      if (state.pinned) {
   window.addEventListener("keydown", function (e) {
        hidePopup(true);
    if (e.key !== "Escape") return;
        e.stopPropagation();
    if (state.open) {
      }
      hidePopup(true);
    },
      e.stopPropagation();
    true
    }
  );
  }, true);
 
   window.addEventListener(
    "keydown",
    function (e) {
      if (e.key !== "Escape") return;
      if (state.open) {
        hidePopup(true);
        e.stopPropagation();
      }
    },
    true
  );


   window.addEventListener(
   window.addEventListener("scroll", function () {
    "scroll",
    if (!state.open || !state.trigger) return;
    function () {
    positionPopupNearTrigger(state.trigger);
      if (!state.open || !state.trigger) return;
  }, true);
      positionPopupNearTrigger(state.trigger);
    },
    true
  );


   window.addEventListener(
   window.addEventListener("resize", function () {
    "resize",
    if (!state.open || !state.trigger) return;
    function () {
    positionPopupNearTrigger(state.trigger);
      if (!state.open || !state.trigger) return;
  }, true);
      positionPopupNearTrigger(state.trigger);
    },
    true
  );


   // Optional: remove native browser tooltips on defs if any remain
   // Remove native title tooltips on defs if any remain
   function stripDefTitles(root) {
   function stripDefTitles(root) {
     var container = root || document;
     var container = root || document;
Line 2,215: Line 2,136:


   if (document.readyState === "loading") {
   if (document.readyState === "loading") {
     document.addEventListener("DOMContentLoaded", function () {
     document.addEventListener("DOMContentLoaded", function () { stripDefTitles(document); });
      stripDefTitles(document);
    });
   } else {
   } else {
     stripDefTitles(document);
     stripDefTitles(document);
   }
   }
})();
})();