コンテンツにスキップ

MediaWiki:MobileUI.js

提供: Vikipedia
2019年6月25日 (火) 01:28時点における𩸽 (トーク | 投稿記録)による版

注意: 保存後、変更を確認するにはブラウザーのキャッシュを消去する必要がある場合があります。

  • Firefox / Safari: Shift を押しながら 再読み込み をクリックするか、Ctrl-F5 または Ctrl-R を押してください (Mac では ⌘-R)
  • Google Chrome: Ctrl-Shift-R を押してください (Mac では ⌘-Shift-R)
  • Microsoft Edge: Ctrl を押しながら 最新の情報に更新 をクリックするか、Ctrl-F5 を押してください。
// @ts-check

/**
 * @typedef {import("jquery")} _JQueryStatic
 */

/**
 * @typedef MenuDataItemComponent
 * @prop {string} [text]
 * @prop {string} [title]
 * @prop {string} [href]
 * @prop {string} [id]
 * @prop {string | string[]} [class]
 * @prop {string} [dataEventName]
 */

/**
 * @typedef MenuDataItem
 * @prop {string} [id]
 * @prop {string | string[]} [class]
 * @prop {MenuDataItemComponent[]} components
 */

/**
 * @typedef MenuDataGroup
 * @prop {string} [id]
 * @prop {string | string[]} [class]
 * @prop {MenuDataItem[]} items
 */

/**
 * @typedef MobileUI
 * @prop {MenuDataGroup[]} leftGroups
 * @prop {MenuDataGroup} leftShortURLGroup
 * @prop {MenuDataGroup[]} rightGroups
 */

class MobileUIBuilder {
    /**
     * @param {any} mw
     * @param {JQueryStatic} $
     */
    constructor(mw, $) {
        this.mw = mw;
        this.$ = $;

        /**
         * @type {string}
         */
        this.wgServer = mw.config.get("wgServer");

        /**
         * @type {string}
         */
        this.wgScriptPath = mw.config.get("wgScriptPath");

        /**
         * @type {number}
         */
        this.wgArticleId = mw.config.get("wgArticleId");
    }

    /**
     * @param {MenuDataItemComponent} component
     * @returns {string}
     */
    hrefForMenuDataItemComponent(component) {
        switch (component.id) {
            case "mobile-ui-page-short-url":
                return (
                    this.wgServer
                    + this.wgScriptPath
                    + "?curid="
                    + String(this.wgArticleId)
                );

            default:
                return component.title
                    ? (this.wgScriptPath + "/" + component.title)
                    : component.href;
        }
    }

    /**
     * @param {MenuDataItemComponent} component
     * @returns {JQuery<HTMLElement>}
     */
    menuDataItemComponent(component) {
        const href = this.hrefForMenuDataItemComponent(component);

        return this
            .$("<a>")
            .text(component.text || href)
            .attr("href", href)
            .attr("id", component.id)
            .addClass(component.class)
            .attr("data-event-name", component.dataEventName);
    }

    /**
     * @param {MenuDataItem} item
     * @returns {JQuery<HTMLElement>}
     */
    menuDataItem(item) {
        /**
         * @type {JQuery<HTMLElement>[]}
         */
        const children = [];

        for (const component of item.components) {
            children.push(this.menuDataItemComponent(component));
        }

        return this
            .$("<li>")
            .attr("id", item.id)
            .addClass(item.class)
            .append(children);
    }

    /**
     * @param {MenuDataGroup} group
     * @returns {JQuery<HTMLElement>}
     */
    menuDataGroup(group) {
        /**
         * @type {JQuery<HTMLElement>[]}
         */
        const children = [];

        for (const item of group.items) {
            children.push(this.menuDataItem(item));
        }

        return this
            .$("<ul>")
            .attr("id", group.id)
            .addClass(group.class)
            .append(children);
    }

    /**
     * @param {MenuDataGroup[]} groups
     * @returns {JQuery<HTMLElement>[]}
     */
    menuDataGroups(groups) {
        /**
         * @type {JQuery<HTMLElement>[]}
         */
        const children = [];

        for (const group of groups) {
            children.push(this.menuDataGroup(group));
        }

        return children;
    }

    /**
     * @param {Node} node
     * @returns {HTMLElement | null}
     */
    elementIfTarget(node) {
        const element = /** @type {HTMLElement} */ (node);
        const { classList } = element;

        if (!classList) return null;
        if (!classList.contains("menu")) return null;
        if (classList.contains("menu-right")) return null;

        return element;
    }

    /**
     * @returns {HTMLElement}
     */
    get navElement() {
        return window.document.getElementById("mw-mf-page-left");
    }

    /**
     * @returns {HTMLElement | null}
     */
    get menuElement() {
        return /** @type {HTMLElement} */ (
            this.navElement.getElementsByClassName("menu").item(0)
        );
    }

    /**
     * @param {MobileUI} mobileUI
     * @param {HTMLElement} menuElement
     */
    modify(mobileUI, menuElement) {
        const leftGroups = this.menuDataGroups(mobileUI.leftGroups);
        const rightGroups = [];

        const jqMenu = this.$(menuElement);
        jqMenu.find("ul").first().remove();
        jqMenu.prepend(leftGroups);

        this
            .$("<div>")
            .addClass(["menu", "menu-right", "view-border-box"])
            .append(rightGroups)
            .insertAfter(jqMenu);

            this
            .$(this.navElement)
            .attr("data-article-id", this.wgArticleId);
    }

    /**
     * @param {MobileUI} mobileUI
     */
    build(mobileUI) {
        const element = this.menuElement;

        if (element) {
            this.modify(mobileUI, element);
            return;
        }

        const observer = new MutationObserver((mutations, observer) => {
            for (const record of mutations) {
                for (const node of record.addedNodes) {
                    const element = this.elementIfTarget(node);
                    if (element) {
                        observer.disconnect();
                        this.modify(mobileUI, element);
                        return;
                    }
                }
            }
        });

        observer.observe(this.navElement, { childList: true });
    }
}
Cookieは私達のサービスを提供するのに役立ちます。このサービスを使用することにより、お客様はCookieの使用に同意するものとします。