跳转到内容

MediaWiki:Gadget-unihan-helper.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/**!
 *  _________________________________________________________________________________
 * |                                                                                 |
 * |                      === WARNING: GLOBAL GADGET FILE ===                        |
 * |                    Changes to this page affect many users.                      |
 * |  Please discuss changes on the talk page, [[WP:VPT]] or GitHub before editing.  |
 * |_________________________________________________________________________________|
 *
 * Built from GitHub repository (https://github.com/Suoerix/unihan-helper), you should not make
 * changes directly here.
 *
 * See https://github.com/Suoerix/unihan-helper/blob/main/README.md for build instructions.
 */
// <nowiki>
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
  for (var prop in b || (b = {}))
    if (__hasOwnProp.call(b, prop))
      __defNormalProp(a, prop, b[prop]);
  if (__getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(b)) {
      if (__propIsEnum.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    }
  return a;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
const API_BASE = "https://webfont-zh.toolforge.org";
const IS_TOUCHSCREEN = "ontouchstart" in document.documentElement;
const STORAGE_KEY = "unihan-settings";
const DEFAULT_FONT = "Plangothic";
const DEFAULT_SETTINGS = {
  enabled: true,
  useWebfont: false,
  loadMode: "always",
  selectedFont: DEFAULT_FONT
};
const CLASSES = {
  OVERLAY: "unihan-overlay",
  INLINE_UNIHAN: "inline-unihan"
};
const TIMINGS = {
  HOVER_DELAY: IS_TOUCHSCREEN ? 0 : 200,
  HIDE_DELAY: 200
};
const fontVersions = {
  "Plangothic": "2.9.5787"
};
async function fetchFontList() {
  const response = await fetch(`${API_BASE}/api/v1/list`);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  const list = await response.json();
  list.forEach((font) => {
    fontVersions[font.id] = font.version;
  });
  return list;
}
function buildFontApiUrl(fontId, codePoint, version) {
  const v = fontVersions[fontId];
  let url = `${API_BASE}/api/v1/font?id=${fontId}&char=${codePoint}`;
  if (v) {
    url += `&v=${v}`;
  }
  return url;
}
function getCodePoint(char) {
  return char.codePointAt(0) || 0;
}
function getHexCodePoint(char) {
  return getCodePoint(char).toString(16).toUpperCase();
}
function getSettings(key, defaults) {
  try {
    const stored = localStorage.getItem(key);
    if (stored) {
      return __spreadValues(__spreadValues({}, defaults), JSON.parse(stored));
    }
    return defaults;
  } catch (e) {
    return defaults;
  }
}
function saveSettings(key, settings2) {
  try {
    localStorage.setItem(key, JSON.stringify(settings2));
  } catch (e) {
    console.error("Failed to save settings:", e);
  }
}
const appliedChars = /* @__PURE__ */ new Set();
let styleElement = null;
function getStyleElement() {
  if (!styleElement) {
    styleElement = document.getElementById("unihan-webfont-styles");
    if (!styleElement) {
      styleElement = document.createElement("style");
      styleElement.id = "unihan-webfont-styles";
      document.head.appendChild(styleElement);
    }
  }
  return styleElement;
}
function applyWebFont(char, fontId, loadMode = "always") {
  const codePoint = getCodePoint(char);
  const hexCodePoint = getHexCodePoint(char);
  const key = `${fontId}-${codePoint}`;
  if (appliedChars.has(key)) {
    return;
  }
  appliedChars.add(key);
  const fontFamily = `${fontId}-${codePoint}`;
  const css = `
@font-face {
  font-family: "${fontFamily}";
  src: url("${buildFontApiUrl(fontId, codePoint)}") format("woff2");
  unicode-range: U+${hexCodePoint};
}`;
  const styleEl = getStyleElement();
  styleEl.textContent += css + "\n";
  document.querySelectorAll(`.${CLASSES.INLINE_UNIHAN}`).forEach((el) => {
    const element = el;
    const text = element.textContent || "";
    if (text.includes(char)) {
      const currentFamily = element.style.fontFamily || "serif";
      if (loadMode === "fallback") {
        element.style.fontFamily = `${currentFamily}, "${fontFamily}"`;
      } else {
        element.style.fontFamily = `"${fontFamily}", ${currentFamily}`;
      }
    }
  });
}
function processUnihanChars(fontId, loadMode = "always") {
  document.querySelectorAll(`.${CLASSES.INLINE_UNIHAN}`).forEach((el) => {
    const text = (el.textContent || "").trim();
    for (const char of text) {
      applyWebFont(char, fontId, loadMode);
    }
  });
}
function clearAppliedFonts() {
  appliedChars.clear();
  const styleEl = getStyleElement();
  styleEl.textContent = "";
}
class Tooltip {
  constructor(element, text, onSettingsClick) {
    __publicField(this, "element");
    __publicField(this, "text");
    __publicField(this, "$tooltip", null);
    __publicField(this, "$content", null);
    __publicField(this, "$tail", null);
    __publicField(this, "isPresent", false);
    __publicField(this, "disappearing", false);
    __publicField(this, "hideTimer", null);
    __publicField(this, "removeTimer", null);
    __publicField(this, "onSettingsClick");
    __publicField(this, "isSettingsLoading", false);
    // 外部定时器清除回调
    __publicField(this, "clearExternalTimers", null);
    this.element = element;
    this.text = text;
    this.onSettingsClick = onSettingsClick;
  }
  show() {
    this.clearTimers();
    this.disappearing = false;
    if (!this.$tooltip) {
      this.create();
    }
    if (this.$tooltip) {
      this.$tooltip.classList.remove("unihan-fade-out-up", "unihan-fade-out-down");
    }
    if (!this.isPresent && this.$tooltip) {
      document.body.appendChild(this.$tooltip);
      this.isPresent = true;
    }
    this.calculatePosition();
  }
  hide() {
    if (!this.$tooltip || !this.isPresent) {
      return;
    }
    this.disappearing = true;
    if (this.$tooltip.classList.contains("unihan-tooltip-above")) {
      this.$tooltip.classList.remove("unihan-fade-in-down");
      this.$tooltip.classList.add("unihan-fade-out-up");
    } else {
      this.$tooltip.classList.remove("unihan-fade-in-up");
      this.$tooltip.classList.add("unihan-fade-out-down");
    }
    this.removeTimer = window.setTimeout(() => {
      if (this.$tooltip && this.isPresent) {
        this.$tooltip.remove();
        this.isPresent = false;
        this.disappearing = false;
      }
    }, 200);
  }
  clearTimers() {
    if (this.hideTimer !== null) {
      clearTimeout(this.hideTimer);
      this.hideTimer = null;
    }
    if (this.removeTimer !== null) {
      clearTimeout(this.removeTimer);
      this.removeTimer = null;
    }
  }
  isVisible() {
    return this.isPresent && !this.disappearing;
  }
  destroy() {
    this.clearTimers();
    if (this.$tooltip) {
      this.$tooltip.remove();
      this.$tooltip = null;
      this.$content = null;
      this.$tail = null;
    }
    this.isPresent = false;
    this.disappearing = false;
  }
  create() {
    this.$tooltip = document.createElement("div");
    this.$tooltip.className = "unihan-tooltip";
    this.$tooltip.setAttribute("role", "tooltip");
    this.$content = document.createElement("div");
    this.$content.className = "unihan-tooltip-content";
    const settingsContainer = document.createElement("div");
    settingsContainer.className = "unihan-settings-btn-container";
    const settingsButton = document.createElement("button");
    settingsButton.className = "unihan-settings-button";
    const settingsLabel = mw.msg("unihan-settings");
    settingsButton.setAttribute("aria-label", settingsLabel);
    settingsButton.setAttribute("title", settingsLabel);
    const settingsIcon = document.createElement("span");
    settingsIcon.className = "unihan-settings-icon";
    settingsButton.appendChild(settingsIcon);
    settingsButton.addEventListener("click", async () => {
      if (this.isSettingsLoading) {
        return;
      }
      this.isSettingsLoading = true;
      settingsButton.disabled = true;
      try {
        await this.onSettingsClick();
      } finally {
        setTimeout(() => {
          this.isSettingsLoading = false;
          settingsButton.disabled = false;
        }, 300);
      }
      this.hide();
    });
    settingsContainer.appendChild(settingsButton);
    const textDiv = document.createElement("div");
    textDiv.className = "unihan-tooltip-text";
    textDiv.textContent = this.text;
    this.$content.appendChild(settingsContainer);
    this.$content.appendChild(textDiv);
    this.$tail = document.createElement("div");
    this.$tail.className = "unihan-tooltip-tail";
    this.$tooltip.appendChild(this.$tail);
    this.$tooltip.appendChild(this.$content);
    if (!IS_TOUCHSCREEN) {
      this.$tooltip.addEventListener("mouseenter", () => {
        this.clearTimers();
        if (this.clearExternalTimers) {
          this.clearExternalTimers();
        }
        this.disappearing = false;
      });
      this.$tooltip.addEventListener("mouseleave", () => {
        this.hideTimer = window.setTimeout(() => {
          this.hide();
        }, TIMINGS.HIDE_DELAY);
      });
    }
  }
  calculatePosition() {
    if (!this.$tooltip || !this.$tail) {
      return;
    }
    const anchorRect = this.element.getBoundingClientRect();
    const tooltipRect = this.$tooltip.getBoundingClientRect();
    const windowWidth = window.innerWidth;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    const anchorCenterX = anchorRect.left + anchorRect.width / 2;
    const anchorTop = anchorRect.top + scrollTop;
    const anchorBottom = anchorRect.bottom + scrollTop;
    let tooltipTop = anchorTop - tooltipRect.height - 7;
    let tooltipLeft = anchorCenterX - 20;
    let isAbove = true;
    if (anchorTop < tooltipRect.height + scrollTop + 6) {
      tooltipTop = anchorBottom + 9;
      isAbove = false;
    }
    if (tooltipLeft + tooltipRect.width > windowWidth + scrollLeft - 1) {
      tooltipLeft = windowWidth + scrollLeft - tooltipRect.width;
    }
    if (tooltipLeft < scrollLeft) {
      tooltipLeft = scrollLeft;
    }
    this.$tooltip.style.top = `${tooltipTop}px`;
    this.$tooltip.style.left = `${tooltipLeft}px`;
    const tailLeft = anchorCenterX - tooltipLeft - 6;
    this.$tail.style.left = `${tailLeft}px`;
    if (isAbove) {
      this.$tooltip.classList.remove("unihan-tooltip-below", "unihan-fade-in-up");
      this.$tooltip.classList.add("unihan-tooltip-above", "unihan-fade-in-down");
    } else {
      this.$tooltip.classList.remove("unihan-tooltip-above", "unihan-fade-in-down");
      this.$tooltip.classList.add("unihan-tooltip-below", "unihan-fade-in-up");
    }
  }
}
const { batchConv } = require("ext.gadget.HanAssist");
mw.messages.set(
  batchConv({
    "unihan-settings": { hans: "设置", hant: "設定" }
  })
);
let availableFonts = null;
let settings = getSettings(STORAGE_KEY, DEFAULT_SETTINGS);
const tooltips = /* @__PURE__ */ new Map();
function checkDisabled() {
  const ep = mw.util.getParamValue("UTdontload");
  if (ep && !isNaN(Number(ep))) {
    $.cookie("UTdontload", "1", { path: "/", expires: parseInt(ep) });
  }
  return $.cookie("UTdontload") === "1";
}
function initOverlay() {
  const overlay = document.createElement("div");
  overlay.className = CLASSES.OVERLAY;
  document.body.appendChild(overlay);
}
async function openSettings() {
  try {
    await mw.loader.using("ext.gadget.unihan-helper-settings");
    const settingsModule = require("ext.gadget.unihan-helper-settings");
    if (settingsModule && typeof settingsModule.openDialog === "function") {
      let fontLoadError = false;
      if (settings.useWebfont && availableFonts === null) {
        try {
          availableFonts = await fetchFontList();
        } catch (error) {
          console.error("Failed to fetch font list:", error);
          fontLoadError = true;
        }
      }
      settingsModule.openDialog(
        availableFonts,
        settings,
        (newSettings) => {
          settings = newSettings;
          saveSettings(STORAGE_KEY, settings);
          if (settings.enabled && settings.useWebfont) {
            clearAppliedFonts();
            processUnihanChars(settings.selectedFont, settings.loadMode);
          } else {
            clearAppliedFonts();
          }
        },
        // 提供字体加载函数
        async () => {
          if (availableFonts === null) {
            availableFonts = await fetchFontList();
          }
          return availableFonts;
        },
        // 传递初始加载错误状态
        fontLoadError
      );
    }
  } catch (error) {
    console.error("Failed to load settings module:", error);
    mw.notify("无法加载设置模块", { type: "error" });
  }
}
function bindInteractions() {
  if (!settings.enabled) {
    return;
  }
  const elements = document.querySelectorAll(`.${CLASSES.INLINE_UNIHAN}`);
  elements.forEach((el) => {
    const element = el;
    const tooltipText = element.getAttribute("title");
    if (!tooltipText) return;
    element.removeAttribute("title");
    const tooltip = new Tooltip(element, tooltipText, openSettings);
    tooltips.set(element, tooltip);
    let showTimer = null;
    let hideTimer = null;
    tooltip.clearExternalTimers = () => {
      if (showTimer !== null) {
        clearTimeout(showTimer);
        showTimer = null;
      }
      if (hideTimer !== null) {
        clearTimeout(hideTimer);
        hideTimer = null;
      }
    };
    const show = () => {
      if (showTimer !== null) {
        clearTimeout(showTimer);
        showTimer = null;
      }
      if (hideTimer !== null) {
        clearTimeout(hideTimer);
        hideTimer = null;
      }
      tooltip.show();
    };
    const hide = () => {
      if (showTimer !== null) {
        clearTimeout(showTimer);
        showTimer = null;
      }
      if (hideTimer !== null) {
        clearTimeout(hideTimer);
      }
      hideTimer = window.setTimeout(() => {
        tooltip.hide();
        hideTimer = null;
      }, TIMINGS.HIDE_DELAY);
    };
    if (IS_TOUCHSCREEN) {
      element.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        tooltips.forEach((t, el2) => {
          if (el2 !== element) {
            t.hide();
          }
        });
        if (tooltip.isVisible()) {
          tooltip.hide();
        } else {
          show();
        }
      });
    } else {
      element.addEventListener("mouseenter", () => {
        showTimer = window.setTimeout(() => show(), TIMINGS.HOVER_DELAY);
      });
      element.addEventListener("mouseleave", hide);
    }
  });
  if (IS_TOUCHSCREEN) {
    document.addEventListener("click", (e) => {
      const clickedTooltip = e.target.closest(".unihan-tooltip");
      const clickedTrigger = e.target.closest(`.${CLASSES.INLINE_UNIHAN}`);
      if (!clickedTooltip && !clickedTrigger) {
        tooltips.forEach((tooltip) => {
          tooltip.hide();
        });
      }
    });
  }
}
async function init() {
  if (checkDisabled()) {
    return;
  }
  initOverlay();
  if (settings.enabled && settings.useWebfont) {
    processUnihanChars(settings.selectedFont, settings.loadMode);
  }
  bindInteractions();
}
if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", init);
} else {
  void init();
}
// </nowiki>