編集の要約なし
編集の要約なし
1行目: 1行目:
/* ========================================
/* ========================================
  * 屋台比較
  * 屋台比較
  * 最大4店舗を localStorage に保存
  *
  * Template側にはbuttonを書かず、
  * ・最大4店舗
  * JavaScriptでbuttonを生成する
  * ・localStorageにはstall_idだけ保存
* ・屋台名はCargo APIから取得
* ・画面下部に固定比較トレイを表示
  * ======================================== */
  * ======================================== */


mw.loader.using( 'mediawiki.storage' ).then( function () {
mw.loader.using( [
    'mediawiki.storage',
    'mediawiki.api',
    'mediawiki.util'
] ).then( function () {


     'use strict';
     'use strict';
13行目: 19行目:
     const MAX_COMPARE = 4;
     const MAX_COMPARE = 4;


     /**
    const api = new mw.Api();
     * 比較候補を取得
 
     /*
    * このページを表示している間だけ使う
     * 屋台名キャッシュ
     */
     */
    const stallNameCache = {};
    /* =====================================
    * localStorage
    * ===================================== */
     function getCompareStalls() {
     function getCompareStalls() {


32行目: 48行目:
             }
             }


            /*
            * 数字だけ許可
            * localStorage改変対策
            */
             return [ ...new Set(
             return [ ...new Set(
                 ids
                 ids
                     .map( String )
                     .map( String )
                     .filter( function ( id ) {
                     .filter( function ( id ) {
                         return id !== '';
                         return /^\d+$/.test( id );
                     } )
                     } )
             ) ].slice( 0, MAX_COMPARE );
             ) ].slice( 0, MAX_COMPARE );
45行目: 65行目:


         }
         }
     }
     }




    /**
    * 比較候補を保存
    */
     function saveCompareStalls( ids ) {
     function saveCompareStalls( ids ) {


61行目: 79行目:




     /**
     /* =====================================
     * メッセージ表示
    * Cargoから屋台名取得
     */
    * ===================================== */
     function showMessage( control, message, isError ) {
 
    function fetchStallNames( ids ) {
 
        if ( ids.length === 0 ) {
            return Promise.resolve( {} );
        }
 
        /*
        * すでに取得済みのID
        */
        const result = {};
 
        const missingIds = [];
 
        ids.forEach( function ( id ) {
 
            if ( stallNameCache[ id ] ) {
 
                result[ id ] =
                    stallNameCache[ id ];
 
            } else {
 
                missingIds.push( id );
 
            }
 
        } );
 
 
        /*
        * 全部キャッシュ済み
        */
        if ( missingIds.length === 0 ) {
            return Promise.resolve( result );
        }
 
 
        /*
        * 数字だけにしてあるため
        * IN (1,2,3) として安全にCargoへ渡す
        */
        const where =
            'stall_id IN (' +
            missingIds.join( ',' ) +
            ')';
 
 
        return api.get( {
 
            action: 'cargoquery',
 
            tables: 'Stalls',
 
            fields:
                'stall_id=stall_id,' +
                'name=stall_name,' +
                '_pageName=page_name',
 
            where: where,
 
            limit: MAX_COMPARE,
 
            format: 'json'
 
        } ).then( function ( data ) {
 
            if (
                !data ||
                !Array.isArray( data.cargoquery )
            ) {
                return result;
            }
 
 
            data.cargoquery.forEach( function ( item ) {
 
                /*
                * Cargo APIは通常
                *
                * {
                *  title: {
                *    stall_id: "...",
                *    stall_name: "..."
                *  }
                * }
                *
                * の形式
                */
                const row =
                    item.title || item;
 
                if (
                    !row ||
                    row.stall_id === undefined
                ) {
                    return;
                }
 
                const id =
                    String( row.stall_id );
 
                const info = {
 
                    id: id,
 
                    name:
                        row.stall_name ||
                        '屋台ID ' + id,
 
                    page:
                        row.page_name || ''
 
                };
 
                stallNameCache[ id ] = info;
                result[ id ] = info;
 
            } );
 
 
            return result;
 
        } ).catch( function () {
 
            /*
            * APIエラーでも
            * 比較機能そのものは壊さない
            */
            missingIds.forEach( function ( id ) {
 
                result[ id ] = {
                    id: id,
                    name: '屋台ID ' + id,
                    page: ''
                };
 
            } );
 
            return result;
 
        } );
 
    }
 
 
    /* =====================================
     * 詳細ページ側メッセージ
     * ===================================== */
 
     function showMessage(
        control,
        message,
        isError
    ) {


         const messageElement =
         const element =
             control.querySelector(
             control.querySelector(
                 '.stall-compare-message'
                 '.stall-compare-message'
             );
             );


         if ( !messageElement ) {
         if ( !element ) {
             return;
             return;
         }
         }


         messageElement.textContent = message;
         element.textContent =
            message;


         messageElement.classList.toggle(
         element.classList.toggle(
             'stall-compare-message-error',
             'stall-compare-message-error',
             Boolean( isError )
             Boolean( isError )
85行目: 258行目:




     /**
     /* =====================================
     * 比較ボタン生成
     * 詳細ページ側button生成
     */
     * ===================================== */
 
     function createCompareButtons() {
     function createCompareButtons() {


96行目: 270行目:
             .forEach( function ( placeholder ) {
             .forEach( function ( placeholder ) {


                /*
                * すでに生成済みなら何もしない
                */
                 if (
                 if (
                     placeholder.dataset.initialized === '1'
                     placeholder.dataset.initialized ===
                    '1'
                 ) {
                 ) {
                     return;
                     return;
                 }
                 }


                /*
                * id:
                * stall-compare-placeholder-1
                * ↓
                * stallId = "1"
                */
                 const prefix =
                 const prefix =
                     'stall-compare-placeholder-';
                     'stall-compare-placeholder-';


                 if (
                 if (
                     !placeholder.id.startsWith( prefix )
                     !placeholder.id.startsWith(
                        prefix
                    )
                 ) {
                 ) {
                     return;
                     return;
125行目: 293行目:
                     );
                     );


                 if ( !stallId ) {
                 if (
                    !/^\d+$/.test( stallId )
                ) {
                     return;
                     return;
                 }
                 }


                /*
                * JavaScript側で本物のbuttonを作る
                */
                 const button =
                 const button =
                     document.createElement( 'button' );
                     document.createElement(
                        'button'
                    );


                 button.type = 'button';
                 button.type =
                    'button';


                 button.className =
                 button.className =
151行目: 321行目:
                     '比較に追加';
                     '比較に追加';


                 placeholder.appendChild( button );
                 placeholder.appendChild(
                    button
                );


                 placeholder.dataset.initialized =
                 placeholder.dataset.initialized =
161行目: 333行目:




     /**
     /* =====================================
     * ボタン状態更新
     * 詳細ページ側button状態
     */
     * ===================================== */
 
     function updateCompareButtons() {
     function updateCompareButtons() {


218行目: 391行目:




     /**
     /* =====================================
     * 比較ボタンクリック
    * 固定比較トレイを生成
     */
     * ===================================== */
 
    function createCompareTray() {
 
        if (
            document.getElementById(
                'stall-compare-tray'
            )
        ) {
            return;
        }
 
        const tray =
            document.createElement( 'div' );
 
        tray.id =
            'stall-compare-tray';
 
        tray.className =
            'stall-compare-tray';
 
        tray.setAttribute(
            'aria-live',
            'polite'
        );
 
 
        /*
        * ヘッダー
        */
        const header =
            document.createElement( 'div' );
 
        header.className =
            'stall-compare-tray-header';
 
 
        const title =
            document.createElement( 'strong' );
 
        title.className =
            'stall-compare-tray-title';
 
        title.textContent =
            '比較候補';
 
 
        const count =
            document.createElement( 'span' );
 
        count.className =
            'stall-compare-tray-count';
 
 
        header.appendChild( title );
        header.appendChild( count );
 
 
        /*
        * 屋台一覧
        */
        const items =
            document.createElement( 'div' );
 
        items.className =
            'stall-compare-tray-items';
 
 
        /*
        * アクション部分
        */
        const actions =
            document.createElement( 'div' );
 
        actions.className =
            'stall-compare-tray-actions';
 
 
        const clearButton =
            document.createElement( 'button' );
 
        clearButton.type =
            'button';
 
        clearButton.className =
            'stall-compare-clear';
 
        clearButton.textContent =
            'すべて外す';
 
 
        const compareLink =
            document.createElement( 'a' );
 
        compareLink.className =
            'stall-compare-open';
 
        compareLink.href =
            mw.util.getUrl( '屋台比較' );
 
        compareLink.textContent =
            '比較する';
 
 
        actions.appendChild(
            clearButton
        );
 
        actions.appendChild(
            compareLink
        );
 
 
        tray.appendChild(
            header
        );
 
        tray.appendChild(
            items
        );
 
        tray.appendChild(
            actions
        );
 
 
        document.body.appendChild(
            tray
        );
 
    }
 
 
    /* =====================================
     * 固定比較トレイ更新
    * ===================================== */
 
    function updateCompareTray() {
 
        createCompareTray();
 
        const tray =
            document.getElementById(
                'stall-compare-tray'
            );
 
        if ( !tray ) {
            return;
        }
 
 
        const ids =
            getCompareStalls();
 
 
        const count =
            tray.querySelector(
                '.stall-compare-tray-count'
            );
 
        const items =
            tray.querySelector(
                '.stall-compare-tray-items'
            );
 
        const compareLink =
            tray.querySelector(
                '.stall-compare-open'
            );
 
 
        if ( count ) {
 
            count.textContent =
                ids.length +
                ' / ' +
                MAX_COMPARE;
 
        }
 
 
        /*
        * 0件ならトレイ非表示
        */
        if ( ids.length === 0 ) {
 
            tray.classList.remove(
                'stall-compare-tray-visible'
            );
 
            if ( items ) {
                items.innerHTML = '';
            }
 
            return;
        }
 
 
        tray.classList.add(
            'stall-compare-tray-visible'
        );
 
 
        /*
        * 2件未満では比較ページを無効化
        */
        if ( compareLink ) {
 
            if ( ids.length >= 2 ) {
 
                compareLink.classList.remove(
                    'stall-compare-open-disabled'
                );
 
                compareLink.setAttribute(
                    'aria-disabled',
                    'false'
                );
 
            } else {
 
                compareLink.classList.add(
                    'stall-compare-open-disabled'
                );
 
                compareLink.setAttribute(
                    'aria-disabled',
                    'true'
                );
 
            }
 
        }
 
 
        if ( !items ) {
            return;
        }
 
 
        /*
        * 読み込み中
        */
        items.textContent =
            '屋台情報を読み込み中…';
 
 
        fetchStallNames(
            ids
        ).then( function ( stallInfo ) {
 
            /*
            * 更新待ち中に選択内容が
            * 変わった場合も現在値を優先
            */
            const currentIds =
                getCompareStalls();
 
            items.innerHTML =
                '';
 
 
            currentIds.forEach(
                function ( id ) {
 
                    const info =
                        stallInfo[ id ] || {
                            id: id,
                            name:
                                '屋台ID ' + id,
                            page: ''
                        };
 
 
                    const item =
                        document.createElement(
                            'div'
                        );
 
                    item.className =
                        'stall-compare-tray-item';
 
 
                    /*
                    * 屋台名
                    */
                    let nameElement;
 
                    if ( info.page ) {
 
                        nameElement =
                            document.createElement(
                                'a'
                            );
 
                        nameElement.href =
                            mw.util.getUrl(
                                info.page
                            );
 
                    } else {
 
                        nameElement =
                            document.createElement(
                                'span'
                            );
 
                    }
 
                    nameElement.className =
                        'stall-compare-tray-item-name';
 
                    nameElement.textContent =
                        info.name;
 
 
                    /*
                    * × ボタン
                    */
                    const removeButton =
                        document.createElement(
                            'button'
                        );
 
                    removeButton.type =
                        'button';
 
                    removeButton.className =
                        'stall-compare-tray-remove';
 
                    removeButton.dataset.stallId =
                        id;
 
                    removeButton.setAttribute(
                        'aria-label',
                        info.name +
                        'を比較候補から外す'
                    );
 
                    removeButton.textContent =
                        '×';
 
 
                    item.appendChild(
                        nameElement
                    );
 
                    item.appendChild(
                        removeButton
                    );
 
                    items.appendChild(
                        item
                    );
 
                }
            );
 
        } );
 
    }
 
 
    /* =====================================
    * 詳細ページ比較button
    * ===================================== */
 
     document.addEventListener(
     document.addEventListener(
         'click',
         'click',
240行目: 779行目:
                     button.dataset.stallId || ''
                     button.dataset.stallId || ''
                 );
                 );
            if (
                !/^\d+$/.test( stallId )
            ) {
                return;
            }


             const control =
             const control =
246行目: 792行目:
                 );
                 );


            if ( !stallId ) {
                if ( control ) {
                    showMessage(
                        control,
                        '屋台IDを取得できませんでした。',
                        true
                    );
                }
                return;
            }


             let ids =
             let ids =
                 getCompareStalls();
                 getCompareStalls();


             const index =
             const index =
                 ids.indexOf( stallId );
                 ids.indexOf(
                    stallId
                );




             /*
             /*
             * 登録済み
             * すでに選択済み
             * → 比較候補から外す
             * → 外す
             */
             */
             if ( index !== -1 ) {
             if ( index !== -1 ) {
284行目: 819行目:


                 updateCompareButtons();
                 updateCompareButtons();
                updateCompareTray();


                 if ( control ) {
                 if ( control ) {
296行目: 832行目:


                 return;
                 return;
             }
             }




             /*
             /*
             * 最大4店舗
             * 4件上限
             */
             */
             if (
             if (
                 ids.length >= MAX_COMPARE
                 ids.length >=
                MAX_COMPARE
             ) {
             ) {


317行目: 855行目:


                 return;
                 return;
             }
             }




            /*
            * 比較候補へ追加
            */
             ids.push(
             ids.push(
                 stallId
                 stallId
332行目: 868行目:


             updateCompareButtons();
             updateCompareButtons();
            updateCompareTray();


             if ( control ) {
             if ( control ) {
349行目: 887行目:




     /**
     /* =====================================
    * トレイ × button
    * ===================================== */
 
    document.addEventListener(
        'click',
        function ( event ) {
 
            const button =
                event.target.closest(
                    '.stall-compare-tray-remove'
                );
 
            if ( !button ) {
                return;
            }
 
            event.preventDefault();
 
            const stallId =
                String(
                    button.dataset.stallId || ''
                );
 
            let ids =
                getCompareStalls();
 
            ids =
                ids.filter(
                    function ( id ) {
                        return id !== stallId;
                    }
                );
 
            saveCompareStalls(
                ids
            );
 
            updateCompareButtons();
            updateCompareTray();
 
        }
    );
 
 
    /* =====================================
    * 「すべて外す」
    * ===================================== */
 
    document.addEventListener(
        'click',
        function ( event ) {
 
            const button =
                event.target.closest(
                    '.stall-compare-clear'
                );
 
            if ( !button ) {
                return;
            }
 
            event.preventDefault();
 
            saveCompareStalls(
                []
            );
 
            updateCompareButtons();
            updateCompareTray();
 
        }
    );
 
 
    /* =====================================
    * 1件時の「比較する」クリック防止
    * ===================================== */
 
    document.addEventListener(
        'click',
        function ( event ) {
 
            const link =
                event.target.closest(
                    '.stall-compare-open-disabled'
                );
 
            if ( !link ) {
                return;
            }
 
            event.preventDefault();
 
        }
    );
 
 
    /* =====================================
     * 初期化
     * 初期化
     */
     * ===================================== */
 
     function initStallCompare() {
     function initStallCompare() {


         createCompareButtons();
         createCompareButtons();
        createCompareTray();
         updateCompareButtons();
         updateCompareButtons();
        updateCompareTray();


     }
     }




    /*
    * 通常ページ
    */
     initStallCompare();
     initStallCompare();




    /*
    * MediaWiki本文再描画時
    */
     mw.hook(
     mw.hook(
         'wikipage.content'
         'wikipage.content'

2026年8月11日 (火) 20:50時点における版

/* ========================================
 * 屋台比較
 *
 * ・最大4店舗
 * ・localStorageにはstall_idだけ保存
 * ・屋台名はCargo APIから取得
 * ・画面下部に固定比較トレイを表示
 * ======================================== */

mw.loader.using( [
    'mediawiki.storage',
    'mediawiki.api',
    'mediawiki.util'
] ).then( function () {

    'use strict';

    const STORAGE_KEY = 'matsuriWikiCompareStalls';
    const MAX_COMPARE = 4;

    const api = new mw.Api();

    /*
     * このページを表示している間だけ使う
     * 屋台名キャッシュ
     */
    const stallNameCache = {};


    /* =====================================
     * localStorage
     * ===================================== */

    function getCompareStalls() {

        const raw = mw.storage.get( STORAGE_KEY );

        if ( !raw ) {
            return [];
        }

        try {

            const ids = JSON.parse( raw );

            if ( !Array.isArray( ids ) ) {
                return [];
            }

            /*
             * 数字だけ許可
             * localStorage改変対策
             */
            return [ ...new Set(
                ids
                    .map( String )
                    .filter( function ( id ) {
                        return /^\d+$/.test( id );
                    } )
            ) ].slice( 0, MAX_COMPARE );

        } catch ( e ) {

            return [];

        }

    }


    function saveCompareStalls( ids ) {

        mw.storage.set(
            STORAGE_KEY,
            JSON.stringify( ids )
        );

    }


    /* =====================================
     * Cargoから屋台名取得
     * ===================================== */

    function fetchStallNames( ids ) {

        if ( ids.length === 0 ) {
            return Promise.resolve( {} );
        }

        /*
         * すでに取得済みのID
         */
        const result = {};

        const missingIds = [];

        ids.forEach( function ( id ) {

            if ( stallNameCache[ id ] ) {

                result[ id ] =
                    stallNameCache[ id ];

            } else {

                missingIds.push( id );

            }

        } );


        /*
         * 全部キャッシュ済み
         */
        if ( missingIds.length === 0 ) {
            return Promise.resolve( result );
        }


        /*
         * 数字だけにしてあるため
         * IN (1,2,3) として安全にCargoへ渡す
         */
        const where =
            'stall_id IN (' +
            missingIds.join( ',' ) +
            ')';


        return api.get( {

            action: 'cargoquery',

            tables: 'Stalls',

            fields:
                'stall_id=stall_id,' +
                'name=stall_name,' +
                '_pageName=page_name',

            where: where,

            limit: MAX_COMPARE,

            format: 'json'

        } ).then( function ( data ) {

            if (
                !data ||
                !Array.isArray( data.cargoquery )
            ) {
                return result;
            }


            data.cargoquery.forEach( function ( item ) {

                /*
                 * Cargo APIは通常
                 *
                 * {
                 *   title: {
                 *     stall_id: "...",
                 *     stall_name: "..."
                 *   }
                 * }
                 *
                 * の形式
                 */
                const row =
                    item.title || item;

                if (
                    !row ||
                    row.stall_id === undefined
                ) {
                    return;
                }

                const id =
                    String( row.stall_id );

                const info = {

                    id: id,

                    name:
                        row.stall_name ||
                        '屋台ID ' + id,

                    page:
                        row.page_name || ''

                };

                stallNameCache[ id ] = info;
                result[ id ] = info;

            } );


            return result;

        } ).catch( function () {

            /*
             * APIエラーでも
             * 比較機能そのものは壊さない
             */
            missingIds.forEach( function ( id ) {

                result[ id ] = {
                    id: id,
                    name: '屋台ID ' + id,
                    page: ''
                };

            } );

            return result;

        } );

    }


    /* =====================================
     * 詳細ページ側メッセージ
     * ===================================== */

    function showMessage(
        control,
        message,
        isError
    ) {

        const element =
            control.querySelector(
                '.stall-compare-message'
            );

        if ( !element ) {
            return;
        }

        element.textContent =
            message;

        element.classList.toggle(
            'stall-compare-message-error',
            Boolean( isError )
        );

    }


    /* =====================================
     * 詳細ページ側button生成
     * ===================================== */

    function createCompareButtons() {

        document
            .querySelectorAll(
                '.stall-compare-placeholder'
            )
            .forEach( function ( placeholder ) {

                if (
                    placeholder.dataset.initialized ===
                    '1'
                ) {
                    return;
                }

                const prefix =
                    'stall-compare-placeholder-';

                if (
                    !placeholder.id.startsWith(
                        prefix
                    )
                ) {
                    return;
                }

                const stallId =
                    placeholder.id.substring(
                        prefix.length
                    );

                if (
                    !/^\d+$/.test( stallId )
                ) {
                    return;
                }

                const button =
                    document.createElement(
                        'button'
                    );

                button.type =
                    'button';

                button.className =
                    'stall-compare-button';

                button.dataset.stallId =
                    stallId;

                button.setAttribute(
                    'aria-pressed',
                    'false'
                );

                button.textContent =
                    '比較に追加';

                placeholder.appendChild(
                    button
                );

                placeholder.dataset.initialized =
                    '1';

            } );

    }


    /* =====================================
     * 詳細ページ側button状態
     * ===================================== */

    function updateCompareButtons() {

        const ids =
            getCompareStalls();

        document
            .querySelectorAll(
                '.stall-compare-button'
            )
            .forEach( function ( button ) {

                const stallId =
                    String(
                        button.dataset.stallId || ''
                    );

                const selected =
                    ids.includes( stallId );

                if ( selected ) {

                    button.textContent =
                        '比較から外す';

                    button.classList.add(
                        'stall-compare-button-selected'
                    );

                    button.setAttribute(
                        'aria-pressed',
                        'true'
                    );

                } else {

                    button.textContent =
                        '比較に追加';

                    button.classList.remove(
                        'stall-compare-button-selected'
                    );

                    button.setAttribute(
                        'aria-pressed',
                        'false'
                    );

                }

            } );

    }


    /* =====================================
     * 固定比較トレイを生成
     * ===================================== */

    function createCompareTray() {

        if (
            document.getElementById(
                'stall-compare-tray'
            )
        ) {
            return;
        }

        const tray =
            document.createElement( 'div' );

        tray.id =
            'stall-compare-tray';

        tray.className =
            'stall-compare-tray';

        tray.setAttribute(
            'aria-live',
            'polite'
        );


        /*
         * ヘッダー
         */
        const header =
            document.createElement( 'div' );

        header.className =
            'stall-compare-tray-header';


        const title =
            document.createElement( 'strong' );

        title.className =
            'stall-compare-tray-title';

        title.textContent =
            '比較候補';


        const count =
            document.createElement( 'span' );

        count.className =
            'stall-compare-tray-count';


        header.appendChild( title );
        header.appendChild( count );


        /*
         * 屋台一覧
         */
        const items =
            document.createElement( 'div' );

        items.className =
            'stall-compare-tray-items';


        /*
         * アクション部分
         */
        const actions =
            document.createElement( 'div' );

        actions.className =
            'stall-compare-tray-actions';


        const clearButton =
            document.createElement( 'button' );

        clearButton.type =
            'button';

        clearButton.className =
            'stall-compare-clear';

        clearButton.textContent =
            'すべて外す';


        const compareLink =
            document.createElement( 'a' );

        compareLink.className =
            'stall-compare-open';

        compareLink.href =
            mw.util.getUrl( '屋台比較' );

        compareLink.textContent =
            '比較する';


        actions.appendChild(
            clearButton
        );

        actions.appendChild(
            compareLink
        );


        tray.appendChild(
            header
        );

        tray.appendChild(
            items
        );

        tray.appendChild(
            actions
        );


        document.body.appendChild(
            tray
        );

    }


    /* =====================================
     * 固定比較トレイ更新
     * ===================================== */

    function updateCompareTray() {

        createCompareTray();

        const tray =
            document.getElementById(
                'stall-compare-tray'
            );

        if ( !tray ) {
            return;
        }


        const ids =
            getCompareStalls();


        const count =
            tray.querySelector(
                '.stall-compare-tray-count'
            );

        const items =
            tray.querySelector(
                '.stall-compare-tray-items'
            );

        const compareLink =
            tray.querySelector(
                '.stall-compare-open'
            );


        if ( count ) {

            count.textContent =
                ids.length +
                ' / ' +
                MAX_COMPARE;

        }


        /*
         * 0件ならトレイ非表示
         */
        if ( ids.length === 0 ) {

            tray.classList.remove(
                'stall-compare-tray-visible'
            );

            if ( items ) {
                items.innerHTML = '';
            }

            return;
        }


        tray.classList.add(
            'stall-compare-tray-visible'
        );


        /*
         * 2件未満では比較ページを無効化
         */
        if ( compareLink ) {

            if ( ids.length >= 2 ) {

                compareLink.classList.remove(
                    'stall-compare-open-disabled'
                );

                compareLink.setAttribute(
                    'aria-disabled',
                    'false'
                );

            } else {

                compareLink.classList.add(
                    'stall-compare-open-disabled'
                );

                compareLink.setAttribute(
                    'aria-disabled',
                    'true'
                );

            }

        }


        if ( !items ) {
            return;
        }


        /*
         * 読み込み中
         */
        items.textContent =
            '屋台情報を読み込み中…';


        fetchStallNames(
            ids
        ).then( function ( stallInfo ) {

            /*
             * 更新待ち中に選択内容が
             * 変わった場合も現在値を優先
             */
            const currentIds =
                getCompareStalls();

            items.innerHTML =
                '';


            currentIds.forEach(
                function ( id ) {

                    const info =
                        stallInfo[ id ] || {
                            id: id,
                            name:
                                '屋台ID ' + id,
                            page: ''
                        };


                    const item =
                        document.createElement(
                            'div'
                        );

                    item.className =
                        'stall-compare-tray-item';


                    /*
                     * 屋台名
                     */
                    let nameElement;

                    if ( info.page ) {

                        nameElement =
                            document.createElement(
                                'a'
                            );

                        nameElement.href =
                            mw.util.getUrl(
                                info.page
                            );

                    } else {

                        nameElement =
                            document.createElement(
                                'span'
                            );

                    }

                    nameElement.className =
                        'stall-compare-tray-item-name';

                    nameElement.textContent =
                        info.name;


                    /*
                     * × ボタン
                     */
                    const removeButton =
                        document.createElement(
                            'button'
                        );

                    removeButton.type =
                        'button';

                    removeButton.className =
                        'stall-compare-tray-remove';

                    removeButton.dataset.stallId =
                        id;

                    removeButton.setAttribute(
                        'aria-label',
                        info.name +
                        'を比較候補から外す'
                    );

                    removeButton.textContent =
                        '×';


                    item.appendChild(
                        nameElement
                    );

                    item.appendChild(
                        removeButton
                    );

                    items.appendChild(
                        item
                    );

                }
            );

        } );

    }


    /* =====================================
     * 詳細ページ比較button
     * ===================================== */

    document.addEventListener(
        'click',
        function ( event ) {

            const button =
                event.target.closest(
                    '.stall-compare-button'
                );

            if ( !button ) {
                return;
            }

            event.preventDefault();

            const stallId =
                String(
                    button.dataset.stallId || ''
                );

            if (
                !/^\d+$/.test( stallId )
            ) {
                return;
            }


            const control =
                button.closest(
                    '.stall-compare-control'
                );


            let ids =
                getCompareStalls();


            const index =
                ids.indexOf(
                    stallId
                );


            /*
             * すでに選択済み
             * → 外す
             */
            if ( index !== -1 ) {

                ids.splice(
                    index,
                    1
                );

                saveCompareStalls(
                    ids
                );

                updateCompareButtons();
                updateCompareTray();

                if ( control ) {

                    showMessage(
                        control,
                        '比較候補から外しました。',
                        false
                    );

                }

                return;

            }


            /*
             * 4件上限
             */
            if (
                ids.length >=
                MAX_COMPARE
            ) {

                if ( control ) {

                    showMessage(
                        control,
                        '比較できる屋台は最大4店舗です。',
                        true
                    );

                }

                return;

            }


            ids.push(
                stallId
            );

            saveCompareStalls(
                ids
            );

            updateCompareButtons();
            updateCompareTray();


            if ( control ) {

                showMessage(
                    control,
                    '比較候補に追加しました(' +
                        ids.length +
                        '/4)。',
                    false
                );

            }

        }
    );


    /* =====================================
     * トレイ × button
     * ===================================== */

    document.addEventListener(
        'click',
        function ( event ) {

            const button =
                event.target.closest(
                    '.stall-compare-tray-remove'
                );

            if ( !button ) {
                return;
            }

            event.preventDefault();

            const stallId =
                String(
                    button.dataset.stallId || ''
                );

            let ids =
                getCompareStalls();

            ids =
                ids.filter(
                    function ( id ) {
                        return id !== stallId;
                    }
                );

            saveCompareStalls(
                ids
            );

            updateCompareButtons();
            updateCompareTray();

        }
    );


    /* =====================================
     * 「すべて外す」
     * ===================================== */

    document.addEventListener(
        'click',
        function ( event ) {

            const button =
                event.target.closest(
                    '.stall-compare-clear'
                );

            if ( !button ) {
                return;
            }

            event.preventDefault();

            saveCompareStalls(
                []
            );

            updateCompareButtons();
            updateCompareTray();

        }
    );


    /* =====================================
     * 1件時の「比較する」クリック防止
     * ===================================== */

    document.addEventListener(
        'click',
        function ( event ) {

            const link =
                event.target.closest(
                    '.stall-compare-open-disabled'
                );

            if ( !link ) {
                return;
            }

            event.preventDefault();

        }
    );


    /* =====================================
     * 初期化
     * ===================================== */

    function initStallCompare() {

        createCompareButtons();

        createCompareTray();

        updateCompareButtons();

        updateCompareTray();

    }


    initStallCompare();


    mw.hook(
        'wikipage.content'
    ).add(
        function () {

            initStallCompare();

        }
    );

} );