<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja">
	<id>https://matsuri-wiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=R12+Common.js+update</id>
	<title>全国の祭り・縁日・屋台情報｜祭りWiki - 利用者の投稿記録 [ja]</title>
	<link rel="self" type="application/atom+xml" href="https://matsuri-wiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=R12+Common.js+update"/>
	<link rel="alternate" type="text/html" href="https://matsuri-wiki.com/index.php?title=%E7%89%B9%E5%88%A5:%E6%8A%95%E7%A8%BF%E8%A8%98%E9%8C%B2/R12_Common.js_update"/>
	<updated>2026-09-17T19:22:21Z</updated>
	<subtitle>利用者の投稿記録</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://matsuri-wiki.com/index.php?title=MediaWiki:Common.js&amp;diff=3517</id>
		<title>MediaWiki:Common.js</title>
		<link rel="alternate" type="text/html" href="https://matsuri-wiki.com/index.php?title=MediaWiki:Common.js&amp;diff=3517"/>
		<updated>2026-09-04T09:28:25Z</updated>

		<summary type="html">&lt;p&gt;R12 Common.js update: R12-03G2-R2 屋台種類カテゴリ絞り込み修正&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ========================================&lt;br /&gt;
 * 屋台比較&lt;br /&gt;
 * placement_id 正式版&lt;br /&gt;
 *&lt;br /&gt;
 * 最大4出店&lt;br /&gt;
 *&lt;br /&gt;
 * localStorage:&lt;br /&gt;
 * matsuriWikiComparePlacements&lt;br /&gt;
 *&lt;br /&gt;
 * 1 placement =&lt;br /&gt;
 * 1 festival + 1 year + 1 venue + 1 stall&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
mw.loader.using( [&lt;br /&gt;
    &#039;mediawiki.storage&#039;,&lt;br /&gt;
    &#039;mediawiki.api&#039;,&lt;br /&gt;
    &#039;mediawiki.util&#039;&lt;br /&gt;
] ).then( function () {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    const STORAGE_KEY =&lt;br /&gt;
        &#039;matsuriWikiComparePlacements&#039;;&lt;br /&gt;
&lt;br /&gt;
    const MAX_COMPARE = 4;&lt;br /&gt;
&lt;br /&gt;
    const api =&lt;br /&gt;
        new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * ページ表示中だけ使うキャッシュ&lt;br /&gt;
     */&lt;br /&gt;
    const placementInfoCache = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * localStorage&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function getComparePlacements() {&lt;br /&gt;
&lt;br /&gt;
        const raw =&lt;br /&gt;
            mw.storage.get(&lt;br /&gt;
                STORAGE_KEY&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if ( !raw ) {&lt;br /&gt;
            return [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
&lt;br /&gt;
            const ids =&lt;br /&gt;
                JSON.parse( raw );&lt;br /&gt;
&lt;br /&gt;
            if ( !Array.isArray( ids ) ) {&lt;br /&gt;
                return [];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return [ ...new Set(&lt;br /&gt;
                ids&lt;br /&gt;
                    .map( String )&lt;br /&gt;
                    .filter(&lt;br /&gt;
                        function ( id ) {&lt;br /&gt;
                            return /^\d+$/.test( id );&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
            ) ].slice(&lt;br /&gt;
                0,&lt;br /&gt;
                MAX_COMPARE&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } catch ( e ) {&lt;br /&gt;
&lt;br /&gt;
            return [];&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function saveComparePlacements( ids ) {&lt;br /&gt;
&lt;br /&gt;
        mw.storage.set(&lt;br /&gt;
            STORAGE_KEY,&lt;br /&gt;
            JSON.stringify( ids )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * Cargo共通処理&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function cargoQuery(&lt;br /&gt;
        table,&lt;br /&gt;
        fields,&lt;br /&gt;
        where,&lt;br /&gt;
        limit&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const params = {&lt;br /&gt;
&lt;br /&gt;
            action: &#039;cargoquery&#039;,&lt;br /&gt;
&lt;br /&gt;
            tables: table,&lt;br /&gt;
&lt;br /&gt;
            fields: fields,&lt;br /&gt;
&lt;br /&gt;
            limit: limit || 100,&lt;br /&gt;
&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if ( where ) {&lt;br /&gt;
            params.where = where;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return api.get(&lt;br /&gt;
            params&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !data ||&lt;br /&gt;
                    !Array.isArray(&lt;br /&gt;
                        data.cargoquery&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    return [];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return data.cargoquery.map(&lt;br /&gt;
                    function ( item ) {&lt;br /&gt;
                        return item.title || item;&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function makeInClause( ids ) {&lt;br /&gt;
&lt;br /&gt;
        return ids&lt;br /&gt;
            .map( String )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( id ) {&lt;br /&gt;
                    return /^\d+$/.test( id );&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .join( &#039;,&#039; );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function uniqueIds( values ) {&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            ...new Set(&lt;br /&gt;
                values&lt;br /&gt;
                    .map( String )&lt;br /&gt;
                    .filter(&lt;br /&gt;
                        function ( id ) {&lt;br /&gt;
                            return /^\d+$/.test( id );&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
            )&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function mapBy( rows, key ) {&lt;br /&gt;
&lt;br /&gt;
        const result = {};&lt;br /&gt;
&lt;br /&gt;
        rows.forEach(&lt;br /&gt;
            function ( row ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    row[ key ] === undefined ||&lt;br /&gt;
                    row[ key ] === null&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                result[&lt;br /&gt;
                    String(&lt;br /&gt;
                        row[ key ]&lt;br /&gt;
                    )&lt;br /&gt;
                ] = row;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return result;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * Placement情報取得&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function fetchPlacementInfo( ids ) {&lt;br /&gt;
&lt;br /&gt;
        if ( ids.length === 0 ) {&lt;br /&gt;
            return Promise.resolve( {} );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const result = {};&lt;br /&gt;
        const missingIds = [];&lt;br /&gt;
&lt;br /&gt;
        ids.forEach(&lt;br /&gt;
            function ( id ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    placementInfoCache[ id ]&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    result[ id ] =&lt;br /&gt;
                        placementInfoCache[ id ];&lt;br /&gt;
&lt;br /&gt;
                } else {&lt;br /&gt;
&lt;br /&gt;
                    missingIds.push( id );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( missingIds.length === 0 ) {&lt;br /&gt;
&lt;br /&gt;
            return Promise.resolve(&lt;br /&gt;
                result&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return cargoQuery(&lt;br /&gt;
&lt;br /&gt;
            &#039;FestivalStallPlacements&#039;,&lt;br /&gt;
&lt;br /&gt;
            &#039;placement_id=placement_id,&#039; +&lt;br /&gt;
            &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
            &#039;festival_id=festival_id,&#039; +&lt;br /&gt;
            &#039;venue_id=venue_id,&#039; +&lt;br /&gt;
            &#039;year=year,&#039; +&lt;br /&gt;
            &#039;location_note=location_note&#039;,&lt;br /&gt;
&lt;br /&gt;
            &#039;placement_id IN (&#039; +&lt;br /&gt;
            makeInClause(&lt;br /&gt;
                missingIds&lt;br /&gt;
            ) +&lt;br /&gt;
            &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
            100&lt;br /&gt;
&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( placements ) {&lt;br /&gt;
&lt;br /&gt;
                const stallIds =&lt;br /&gt;
                    uniqueIds(&lt;br /&gt;
                        placements.map(&lt;br /&gt;
                            function ( row ) {&lt;br /&gt;
                                return row.stall_id;&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const festivalIds =&lt;br /&gt;
                    uniqueIds(&lt;br /&gt;
                        placements.map(&lt;br /&gt;
                            function ( row ) {&lt;br /&gt;
                                return row.festival_id;&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const venueIds =&lt;br /&gt;
                    uniqueIds(&lt;br /&gt;
                        placements.map(&lt;br /&gt;
                            function ( row ) {&lt;br /&gt;
                                return row.venue_id;&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                return Promise.all( [&lt;br /&gt;
&lt;br /&gt;
                    stallIds.length&lt;br /&gt;
                        ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                            &#039;Stalls&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
                            &#039;name=stall_name,&#039; +&lt;br /&gt;
                            &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;stall_id IN (&#039; +&lt;br /&gt;
                            makeInClause(&lt;br /&gt;
                                stallIds&lt;br /&gt;
                            ) +&lt;br /&gt;
                            &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                            100&lt;br /&gt;
&lt;br /&gt;
                        )&lt;br /&gt;
                        : Promise.resolve( [] ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    festivalIds.length&lt;br /&gt;
                        ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                            &#039;Festivals&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;festival_id=festival_id,&#039; +&lt;br /&gt;
                            &#039;name=festival_name,&#039; +&lt;br /&gt;
                            &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;festival_id IN (&#039; +&lt;br /&gt;
                            makeInClause(&lt;br /&gt;
                                festivalIds&lt;br /&gt;
                            ) +&lt;br /&gt;
                            &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                            100&lt;br /&gt;
&lt;br /&gt;
                        )&lt;br /&gt;
                        : Promise.resolve( [] ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    venueIds.length&lt;br /&gt;
                        ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                            &#039;Venues&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;venue_id=venue_id,&#039; +&lt;br /&gt;
                            &#039;name=venue_name,&#039; +&lt;br /&gt;
                            &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                            &#039;venue_id IN (&#039; +&lt;br /&gt;
                            makeInClause(&lt;br /&gt;
                                venueIds&lt;br /&gt;
                            ) +&lt;br /&gt;
                            &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                            100&lt;br /&gt;
&lt;br /&gt;
                        )&lt;br /&gt;
                        : Promise.resolve( [] )&lt;br /&gt;
&lt;br /&gt;
                ] ).then(&lt;br /&gt;
                    function ( related ) {&lt;br /&gt;
&lt;br /&gt;
                        return {&lt;br /&gt;
&lt;br /&gt;
                            placements:&lt;br /&gt;
                                placements,&lt;br /&gt;
&lt;br /&gt;
                            stalls:&lt;br /&gt;
                                related[ 0 ],&lt;br /&gt;
&lt;br /&gt;
                            festivals:&lt;br /&gt;
                                related[ 1 ],&lt;br /&gt;
&lt;br /&gt;
                            venues:&lt;br /&gt;
                                related[ 2 ]&lt;br /&gt;
&lt;br /&gt;
                        };&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                const stallMap =&lt;br /&gt;
                    mapBy(&lt;br /&gt;
                        data.stalls,&lt;br /&gt;
                        &#039;stall_id&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const festivalMap =&lt;br /&gt;
                    mapBy(&lt;br /&gt;
                        data.festivals,&lt;br /&gt;
                        &#039;festival_id&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const venueMap =&lt;br /&gt;
                    mapBy(&lt;br /&gt;
                        data.venues,&lt;br /&gt;
                        &#039;venue_id&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                data.placements.forEach(&lt;br /&gt;
                    function ( placement ) {&lt;br /&gt;
&lt;br /&gt;
                        const placementId =&lt;br /&gt;
                            String(&lt;br /&gt;
                                placement.placement_id&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        const info = {&lt;br /&gt;
&lt;br /&gt;
                            placement:&lt;br /&gt;
                                placement,&lt;br /&gt;
&lt;br /&gt;
                            stall:&lt;br /&gt;
                                stallMap[&lt;br /&gt;
                                    String(&lt;br /&gt;
                                        placement.stall_id&lt;br /&gt;
                                    )&lt;br /&gt;
                                ] || null,&lt;br /&gt;
&lt;br /&gt;
                            festival:&lt;br /&gt;
                                festivalMap[&lt;br /&gt;
                                    String(&lt;br /&gt;
                                        placement.festival_id&lt;br /&gt;
                                    )&lt;br /&gt;
                                ] || null,&lt;br /&gt;
&lt;br /&gt;
                            venue:&lt;br /&gt;
                                venueMap[&lt;br /&gt;
                                    String(&lt;br /&gt;
                                        placement.venue_id&lt;br /&gt;
                                    )&lt;br /&gt;
                                ] || null&lt;br /&gt;
&lt;br /&gt;
                        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        placementInfoCache[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] = info;&lt;br /&gt;
&lt;br /&gt;
                        result[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] = info;&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                return result;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * メッセージ&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function showMessage(&lt;br /&gt;
        control,&lt;br /&gt;
        message,&lt;br /&gt;
        isError&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const element =&lt;br /&gt;
            control.querySelector(&lt;br /&gt;
                &#039;.stall-compare-message&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if ( !element ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        element.textContent =&lt;br /&gt;
            message;&lt;br /&gt;
&lt;br /&gt;
        element.classList.toggle(&lt;br /&gt;
            &#039;stall-compare-message-error&#039;,&lt;br /&gt;
            Boolean( isError )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 比較ボタン生成&lt;br /&gt;
 *&lt;br /&gt;
 * 通常ページ&lt;br /&gt;
 *   stall-compare-placeholder-1&lt;br /&gt;
 *&lt;br /&gt;
 * 地図popup&lt;br /&gt;
 *   stall-map-compare-placeholder-1&lt;br /&gt;
 *&lt;br /&gt;
 * の両方に対応&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function createCompareButtons() {&lt;br /&gt;
&lt;br /&gt;
    document&lt;br /&gt;
        .querySelectorAll(&lt;br /&gt;
            &#039;.stall-compare-placeholder, &#039; +&lt;br /&gt;
            &#039;.stall-map-compare-placeholder&#039;&lt;br /&gt;
        )&lt;br /&gt;
        .forEach(&lt;br /&gt;
            function ( placeholder ) {&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * すでにbutton生成済み&lt;br /&gt;
                 */&lt;br /&gt;
                if (&lt;br /&gt;
                    placeholder.dataset.initialized ===&lt;br /&gt;
                    &#039;1&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                let placementId = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 通常ページ&lt;br /&gt;
                 */&lt;br /&gt;
                const normalPrefix =&lt;br /&gt;
                    &#039;stall-compare-placeholder-&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 地図popup&lt;br /&gt;
                 */&lt;br /&gt;
                const mapPrefix =&lt;br /&gt;
                    &#039;stall-map-compare-placeholder-&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    placeholder.id.startsWith(&lt;br /&gt;
                        normalPrefix&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    placementId =&lt;br /&gt;
                        placeholder.id.substring(&lt;br /&gt;
                            normalPrefix.length&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                } else if (&lt;br /&gt;
                    placeholder.id.startsWith(&lt;br /&gt;
                        mapPrefix&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    placementId =&lt;br /&gt;
                        placeholder.id.substring(&lt;br /&gt;
                            mapPrefix.length&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                } else {&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * placement_idチェック&lt;br /&gt;
                 */&lt;br /&gt;
                if (&lt;br /&gt;
                    !/^\d+$/.test(&lt;br /&gt;
                        placementId&lt;br /&gt;
                    ) ||&lt;br /&gt;
                    placementId === &#039;0&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 本物のbuttonを&lt;br /&gt;
                 * JavaScript側で作る&lt;br /&gt;
                 */&lt;br /&gt;
                const button =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;button&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                button.type =&lt;br /&gt;
                    &#039;button&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                button.className =&lt;br /&gt;
                    &#039;stall-compare-button&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                button.dataset.placementId =&lt;br /&gt;
                    placementId;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                button.setAttribute(&lt;br /&gt;
                    &#039;aria-pressed&#039;,&lt;br /&gt;
                    &#039;false&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                button.textContent =&lt;br /&gt;
                    &#039;比較に追加&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                placeholder.appendChild(&lt;br /&gt;
                    button&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 二重生成防止&lt;br /&gt;
                 */&lt;br /&gt;
                placeholder.dataset.initialized =&lt;br /&gt;
                    &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * ボタン状態更新&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function updateCompareButtons() {&lt;br /&gt;
&lt;br /&gt;
        const ids =&lt;br /&gt;
            getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        document&lt;br /&gt;
            .querySelectorAll(&lt;br /&gt;
                &#039;.stall-compare-button&#039;&lt;br /&gt;
            )&lt;br /&gt;
            .forEach(&lt;br /&gt;
                function ( button ) {&lt;br /&gt;
&lt;br /&gt;
                    const placementId =&lt;br /&gt;
                        String(&lt;br /&gt;
                            button.dataset&lt;br /&gt;
                                .placementId || &#039;&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    const selected =&lt;br /&gt;
                        ids.includes(&lt;br /&gt;
                            placementId&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    if ( selected ) {&lt;br /&gt;
&lt;br /&gt;
                        button.textContent =&lt;br /&gt;
                            &#039;比較から外す&#039;;&lt;br /&gt;
&lt;br /&gt;
                        button.classList.add(&lt;br /&gt;
                            &#039;stall-compare-button-selected&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        button.setAttribute(&lt;br /&gt;
                            &#039;aria-pressed&#039;,&lt;br /&gt;
                            &#039;true&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    } else {&lt;br /&gt;
&lt;br /&gt;
                        button.textContent =&lt;br /&gt;
                            &#039;比較に追加&#039;;&lt;br /&gt;
&lt;br /&gt;
                        button.classList.remove(&lt;br /&gt;
                            &#039;stall-compare-button-selected&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        button.setAttribute(&lt;br /&gt;
                            &#039;aria-pressed&#039;,&lt;br /&gt;
                            &#039;false&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 固定比較トレイ生成&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function createCompareTray() {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                &#039;stall-compare-tray&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const tray =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        tray.id =&lt;br /&gt;
            &#039;stall-compare-tray&#039;;&lt;br /&gt;
&lt;br /&gt;
        tray.className =&lt;br /&gt;
            &#039;stall-compare-tray&#039;;&lt;br /&gt;
&lt;br /&gt;
        tray.setAttribute(&lt;br /&gt;
            &#039;aria-live&#039;,&lt;br /&gt;
            &#039;polite&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const header =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        header.className =&lt;br /&gt;
            &#039;stall-compare-tray-header&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const title =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;strong&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        title.className =&lt;br /&gt;
            &#039;stall-compare-tray-title&#039;;&lt;br /&gt;
&lt;br /&gt;
        title.textContent =&lt;br /&gt;
            &#039;比較候補&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const count =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;span&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        count.className =&lt;br /&gt;
            &#039;stall-compare-tray-count&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        header.appendChild(&lt;br /&gt;
            title&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        header.appendChild(&lt;br /&gt;
            count&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const items =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        items.className =&lt;br /&gt;
            &#039;stall-compare-tray-items&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const actions =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        actions.className =&lt;br /&gt;
            &#039;stall-compare-tray-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const clearButton =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;button&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        clearButton.type =&lt;br /&gt;
            &#039;button&#039;;&lt;br /&gt;
&lt;br /&gt;
        clearButton.className =&lt;br /&gt;
            &#039;stall-compare-clear&#039;;&lt;br /&gt;
&lt;br /&gt;
        clearButton.textContent =&lt;br /&gt;
            &#039;すべて外す&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const compareLink =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;a&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        compareLink.className =&lt;br /&gt;
            &#039;stall-compare-open&#039;;&lt;br /&gt;
&lt;br /&gt;
        compareLink.href =&lt;br /&gt;
            mw.util.getUrl(&lt;br /&gt;
                &#039;屋台比較&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        compareLink.textContent =&lt;br /&gt;
            &#039;比較する&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        actions.appendChild(&lt;br /&gt;
            clearButton&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        actions.appendChild(&lt;br /&gt;
            compareLink&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        tray.appendChild(&lt;br /&gt;
            header&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        tray.appendChild(&lt;br /&gt;
            items&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        tray.appendChild(&lt;br /&gt;
            actions&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(&lt;br /&gt;
            tray&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 比較トレイ更新&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function updateCompareTray() {&lt;br /&gt;
&lt;br /&gt;
        createCompareTray();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const tray =&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                &#039;stall-compare-tray&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if ( !tray ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const ids =&lt;br /&gt;
            getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const count =&lt;br /&gt;
            tray.querySelector(&lt;br /&gt;
                &#039;.stall-compare-tray-count&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const items =&lt;br /&gt;
            tray.querySelector(&lt;br /&gt;
                &#039;.stall-compare-tray-items&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const compareLink =&lt;br /&gt;
            tray.querySelector(&lt;br /&gt;
                &#039;.stall-compare-open&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( count ) {&lt;br /&gt;
&lt;br /&gt;
            count.textContent =&lt;br /&gt;
                ids.length +&lt;br /&gt;
                &#039; / &#039; +&lt;br /&gt;
                MAX_COMPARE;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( ids.length === 0 ) {&lt;br /&gt;
&lt;br /&gt;
            tray.classList.remove(&lt;br /&gt;
                &#039;stall-compare-tray-visible&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            if ( items ) {&lt;br /&gt;
                items.innerHTML = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        tray.classList.add(&lt;br /&gt;
            &#039;stall-compare-tray-visible&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( compareLink ) {&lt;br /&gt;
&lt;br /&gt;
            if ( ids.length &amp;gt;= 2 ) {&lt;br /&gt;
&lt;br /&gt;
                compareLink.classList.remove(&lt;br /&gt;
                    &#039;stall-compare-open-disabled&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                compareLink.setAttribute(&lt;br /&gt;
                    &#039;aria-disabled&#039;,&lt;br /&gt;
                    &#039;false&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            } else {&lt;br /&gt;
&lt;br /&gt;
                compareLink.classList.add(&lt;br /&gt;
                    &#039;stall-compare-open-disabled&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                compareLink.setAttribute(&lt;br /&gt;
                    &#039;aria-disabled&#039;,&lt;br /&gt;
                    &#039;true&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( !items ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        items.textContent =&lt;br /&gt;
            &#039;屋台情報を読み込み中…&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        fetchPlacementInfo(&lt;br /&gt;
            ids&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( placementInfo ) {&lt;br /&gt;
&lt;br /&gt;
                const currentIds =&lt;br /&gt;
                    getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
                items.innerHTML =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                currentIds.forEach(&lt;br /&gt;
                    function ( placementId ) {&lt;br /&gt;
&lt;br /&gt;
                        const info =&lt;br /&gt;
                            placementInfo[&lt;br /&gt;
                                placementId&lt;br /&gt;
                            ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if ( !info ) {&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const item =&lt;br /&gt;
                            document.createElement(&lt;br /&gt;
                                &#039;div&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        item.className =&lt;br /&gt;
                            &#039;stall-compare-tray-item&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const text =&lt;br /&gt;
                            document.createElement(&lt;br /&gt;
                                &#039;div&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const name =&lt;br /&gt;
                            document.createElement(&lt;br /&gt;
                                info.stall &amp;amp;&amp;amp;&lt;br /&gt;
                                info.stall.page_name&lt;br /&gt;
                                    ? &#039;a&#039;&lt;br /&gt;
                                    : &#039;span&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            info.stall &amp;amp;&amp;amp;&lt;br /&gt;
                            info.stall.page_name&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            name.href =&lt;br /&gt;
                                mw.util.getUrl(&lt;br /&gt;
                                    info.stall.page_name&lt;br /&gt;
                                );&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        name.className =&lt;br /&gt;
                            &#039;stall-compare-tray-item-name&#039;;&lt;br /&gt;
&lt;br /&gt;
                        name.textContent =&lt;br /&gt;
                            info.stall&lt;br /&gt;
                                ? info.stall.stall_name&lt;br /&gt;
                                : &#039;屋台&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const context =&lt;br /&gt;
                            document.createElement(&lt;br /&gt;
                                &#039;div&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        context.className =&lt;br /&gt;
                            &#039;stall-compare-tray-item-context&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const parts = [];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            info.placement &amp;amp;&amp;amp;&lt;br /&gt;
                            info.placement.year&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            parts.push(&lt;br /&gt;
                                info.placement.year +&lt;br /&gt;
                                &#039;年&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            info.festival &amp;amp;&amp;amp;&lt;br /&gt;
                            info.festival.festival_name&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            parts.push(&lt;br /&gt;
                                info.festival&lt;br /&gt;
                                    .festival_name&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            info.venue &amp;amp;&amp;amp;&lt;br /&gt;
                            info.venue.venue_name&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            parts.push(&lt;br /&gt;
                                info.venue&lt;br /&gt;
                                    .venue_name&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        context.textContent =&lt;br /&gt;
                            parts.join(&lt;br /&gt;
                                &#039; / &#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        text.appendChild(&lt;br /&gt;
                            name&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        text.appendChild(&lt;br /&gt;
                            context&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const removeButton =&lt;br /&gt;
                            document.createElement(&lt;br /&gt;
                                &#039;button&#039;&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        removeButton.type =&lt;br /&gt;
                            &#039;button&#039;;&lt;br /&gt;
&lt;br /&gt;
                        removeButton.className =&lt;br /&gt;
                            &#039;stall-compare-tray-remove&#039;;&lt;br /&gt;
&lt;br /&gt;
                        removeButton.dataset.placementId =&lt;br /&gt;
                            placementId;&lt;br /&gt;
&lt;br /&gt;
                        removeButton.setAttribute(&lt;br /&gt;
                            &#039;aria-label&#039;,&lt;br /&gt;
                            (&lt;br /&gt;
                                info.stall&lt;br /&gt;
                                    ? info.stall.stall_name&lt;br /&gt;
                                    : &#039;屋台&#039;&lt;br /&gt;
                            ) +&lt;br /&gt;
                            &#039;を比較候補から外す&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        removeButton.textContent =&lt;br /&gt;
                            &#039;×&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        item.appendChild(&lt;br /&gt;
                            text&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        item.appendChild(&lt;br /&gt;
                            removeButton&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        items.appendChild(&lt;br /&gt;
                            item&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        ).catch(&lt;br /&gt;
            function ( error ) {&lt;br /&gt;
&lt;br /&gt;
                console.error(&lt;br /&gt;
                    &#039;比較トレイ取得エラー:&#039;,&lt;br /&gt;
                    error&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                items.textContent =&lt;br /&gt;
                    &#039;比較候補を読み込めませんでした。&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 詳細ページ&lt;br /&gt;
     * 「比較に追加」&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&lt;br /&gt;
        &#039;click&#039;,&lt;br /&gt;
        function ( event ) {&lt;br /&gt;
&lt;br /&gt;
            const button =&lt;br /&gt;
                event.target.closest(&lt;br /&gt;
                    &#039;.stall-compare-button&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if ( !button ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const placementId =&lt;br /&gt;
                String(&lt;br /&gt;
                    button.dataset&lt;br /&gt;
                        .placementId || &#039;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !/^\d+$/.test(&lt;br /&gt;
                    placementId&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const control =&lt;br /&gt;
                button.closest(&lt;br /&gt;
                    &#039;.stall-compare-control&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            let ids =&lt;br /&gt;
                getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const index =&lt;br /&gt;
                ids.indexOf(&lt;br /&gt;
                    placementId&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * すでに選択中&lt;br /&gt;
             */&lt;br /&gt;
            if ( index !== -1 ) {&lt;br /&gt;
&lt;br /&gt;
                ids.splice(&lt;br /&gt;
                    index,&lt;br /&gt;
                    1&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                saveComparePlacements(&lt;br /&gt;
                    ids&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                updateCompareButtons();&lt;br /&gt;
                updateMapCompareLinks();&lt;br /&gt;
                updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if ( control ) {&lt;br /&gt;
&lt;br /&gt;
                    showMessage(&lt;br /&gt;
                        control,&lt;br /&gt;
                        &#039;比較候補から外しました。&#039;,&lt;br /&gt;
                        false&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 最大4件&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                ids.length &amp;gt;=&lt;br /&gt;
                MAX_COMPARE&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                if ( control ) {&lt;br /&gt;
&lt;br /&gt;
                    showMessage(&lt;br /&gt;
                        control,&lt;br /&gt;
                        &#039;比較できる出店は最大4件です。&#039;,&lt;br /&gt;
                        true&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            ids.push(&lt;br /&gt;
                placementId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            saveComparePlacements(&lt;br /&gt;
                ids&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            updateCompareButtons();&lt;br /&gt;
            updateMapCompareLinks();&lt;br /&gt;
            updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if ( control ) {&lt;br /&gt;
&lt;br /&gt;
                showMessage(&lt;br /&gt;
                    control,&lt;br /&gt;
                    &#039;比較候補に追加しました（&#039; +&lt;br /&gt;
                    ids.length +&lt;br /&gt;
                    &#039;/4）。&#039;,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * トレイから1件削除&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&lt;br /&gt;
        &#039;click&#039;,&lt;br /&gt;
        function ( event ) {&lt;br /&gt;
&lt;br /&gt;
            const button =&lt;br /&gt;
                event.target.closest(&lt;br /&gt;
                    &#039;.stall-compare-tray-remove&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if ( !button ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const placementId =&lt;br /&gt;
                String(&lt;br /&gt;
                    button.dataset&lt;br /&gt;
                        .placementId || &#039;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            let ids =&lt;br /&gt;
                getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            ids =&lt;br /&gt;
                ids.filter(&lt;br /&gt;
                    function ( id ) {&lt;br /&gt;
                        return id !== placementId;&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            saveComparePlacements(&lt;br /&gt;
                ids&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            updateCompareButtons();&lt;br /&gt;
            updateMapCompareLinks();&lt;br /&gt;
            updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 全削除&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&lt;br /&gt;
        &#039;click&#039;,&lt;br /&gt;
        function ( event ) {&lt;br /&gt;
&lt;br /&gt;
            const button =&lt;br /&gt;
                event.target.closest(&lt;br /&gt;
                    &#039;.stall-compare-clear&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if ( !button ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            saveComparePlacements(&lt;br /&gt;
                []&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            updateCompareButtons();&lt;br /&gt;
            updateMapCompareLinks();&lt;br /&gt;
            updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 1件時の比較リンク無効&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&lt;br /&gt;
        &#039;click&#039;,&lt;br /&gt;
        function ( event ) {&lt;br /&gt;
&lt;br /&gt;
            const link =&lt;br /&gt;
                event.target.closest(&lt;br /&gt;
                    &#039;.stall-compare-open-disabled&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if ( !link ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 初期化&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function initPlacementCompare() {&lt;br /&gt;
&lt;br /&gt;
    createCompareButtons();&lt;br /&gt;
&lt;br /&gt;
    createCompareTray();&lt;br /&gt;
&lt;br /&gt;
    updateCompareButtons();&lt;br /&gt;
&lt;br /&gt;
    updateMapCompareLinks();&lt;br /&gt;
&lt;br /&gt;
    updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
initPlacementCompare();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
mw.hook(&lt;br /&gt;
    &#039;wikipage.content&#039;&lt;br /&gt;
).add(&lt;br /&gt;
    function () {&lt;br /&gt;
&lt;br /&gt;
        initPlacementCompare();&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 地図popup 比較リンク&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function getMapComparePlacementId( link ) {&lt;br /&gt;
&lt;br /&gt;
    if ( !link ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const href =&lt;br /&gt;
        link.getAttribute( &#039;href&#039; ) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    const match =&lt;br /&gt;
        href.match(&lt;br /&gt;
            /#compare-placement-(\d+)$/&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    if ( !match ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return match[ 1 ];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateMapCompareLinks() {&lt;br /&gt;
&lt;br /&gt;
    const ids =&lt;br /&gt;
        getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
    document&lt;br /&gt;
        .querySelectorAll(&lt;br /&gt;
            &#039;a[href*=&amp;quot;#compare-placement-&amp;quot;]&#039;&lt;br /&gt;
        )&lt;br /&gt;
        .forEach(&lt;br /&gt;
            function ( link ) {&lt;br /&gt;
&lt;br /&gt;
                const placementId =&lt;br /&gt;
                    getMapComparePlacementId(&lt;br /&gt;
                        link&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !placementId ||&lt;br /&gt;
                    placementId === &#039;0&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const selected =&lt;br /&gt;
                    ids.includes(&lt;br /&gt;
                        placementId&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                link.classList.add(&lt;br /&gt;
                    &#039;stall-map-compare-link&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                link.dataset.placementId =&lt;br /&gt;
                    placementId;&lt;br /&gt;
&lt;br /&gt;
                if ( selected ) {&lt;br /&gt;
&lt;br /&gt;
                    link.textContent =&lt;br /&gt;
                        &#039;比較から外す&#039;;&lt;br /&gt;
&lt;br /&gt;
                    link.classList.add(&lt;br /&gt;
                        &#039;stall-map-compare-link-selected&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    link.setAttribute(&lt;br /&gt;
                        &#039;aria-pressed&#039;,&lt;br /&gt;
                        &#039;true&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                } else {&lt;br /&gt;
&lt;br /&gt;
                    link.textContent =&lt;br /&gt;
                        &#039;比較に追加&#039;;&lt;br /&gt;
&lt;br /&gt;
                    link.classList.remove(&lt;br /&gt;
                        &#039;stall-map-compare-link-selected&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    link.setAttribute(&lt;br /&gt;
                        &#039;aria-pressed&#039;,&lt;br /&gt;
                        &#039;false&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 地図popupクリック&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
document.addEventListener(&lt;br /&gt;
    &#039;click&#039;,&lt;br /&gt;
    function ( event ) {&lt;br /&gt;
&lt;br /&gt;
        const link =&lt;br /&gt;
            event.target.closest(&lt;br /&gt;
                &#039;a[href*=&amp;quot;#compare-placement-&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if ( !link ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const placementId =&lt;br /&gt;
            getMapComparePlacementId(&lt;br /&gt;
                link&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !placementId ||&lt;br /&gt;
            placementId === &#039;0&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
        let ids =&lt;br /&gt;
            getComparePlacements();&lt;br /&gt;
&lt;br /&gt;
        const index =&lt;br /&gt;
            ids.indexOf(&lt;br /&gt;
                placementId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if ( index !== -1 ) {&lt;br /&gt;
&lt;br /&gt;
            ids.splice(&lt;br /&gt;
                index,&lt;br /&gt;
                1&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } else {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                ids.length &amp;gt;=&lt;br /&gt;
                MAX_COMPARE&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                link.textContent =&lt;br /&gt;
                    &#039;最大4件までです&#039;;&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            ids.push(&lt;br /&gt;
                placementId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        saveComparePlacements(&lt;br /&gt;
            ids&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        updateCompareButtons();&lt;br /&gt;
        updateMapCompareLinks();&lt;br /&gt;
        updateCompareTray();&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * Leaflet popup生成監視&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
const mapCompareLinkObserver =&lt;br /&gt;
    new MutationObserver(&lt;br /&gt;
        function ( mutations ) {&lt;br /&gt;
&lt;br /&gt;
            let needsUpdate =&lt;br /&gt;
                false;&lt;br /&gt;
&lt;br /&gt;
            mutations.forEach(&lt;br /&gt;
                function ( mutation ) {&lt;br /&gt;
&lt;br /&gt;
                    mutation.addedNodes.forEach(&lt;br /&gt;
                        function ( node ) {&lt;br /&gt;
&lt;br /&gt;
                            if (&lt;br /&gt;
                                node.nodeType !== 1&lt;br /&gt;
                            ) {&lt;br /&gt;
                                return;&lt;br /&gt;
                            }&lt;br /&gt;
&lt;br /&gt;
                            if (&lt;br /&gt;
                                node.matches &amp;amp;&amp;amp;&lt;br /&gt;
                                node.matches(&lt;br /&gt;
                                    &#039;a[href*=&amp;quot;#compare-placement-&amp;quot;]&#039;&lt;br /&gt;
                                )&lt;br /&gt;
                            ) {&lt;br /&gt;
&lt;br /&gt;
                                needsUpdate =&lt;br /&gt;
                                    true;&lt;br /&gt;
&lt;br /&gt;
                                return;&lt;br /&gt;
                            }&lt;br /&gt;
&lt;br /&gt;
                            if (&lt;br /&gt;
                                node.querySelector &amp;amp;&amp;amp;&lt;br /&gt;
                                node.querySelector(&lt;br /&gt;
                                    &#039;a[href*=&amp;quot;#compare-placement-&amp;quot;]&#039;&lt;br /&gt;
                                )&lt;br /&gt;
                            ) {&lt;br /&gt;
&lt;br /&gt;
                                needsUpdate =&lt;br /&gt;
                                    true;&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if ( needsUpdate ) {&lt;br /&gt;
&lt;br /&gt;
                updateMapCompareLinks();&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
mapCompareLinkObserver.observe(&lt;br /&gt;
    document.body,&lt;br /&gt;
    {&lt;br /&gt;
        childList: true,&lt;br /&gt;
        subtree: true&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * ★ この } ); が&lt;br /&gt;
 * placement比較用mw.loaderの終了&lt;br /&gt;
 */&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
/* ========================================&lt;br /&gt;
 * 祭りページ&lt;br /&gt;
 * 屋台名・商品名検索&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
mw.loader.using( [&lt;br /&gt;
    &#039;mediawiki.api&#039;&lt;br /&gt;
] ).then( function () {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const cards =&lt;br /&gt;
        Array.from(&lt;br /&gt;
            document.querySelectorAll(&lt;br /&gt;
                &#039;.festival-stall-search-card&#039;&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 屋台カードがないページでは終了&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        cards.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const api =&lt;br /&gt;
        new mw.Api();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 文字列正規化&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function normalizeSearchText(&lt;br /&gt;
        value&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value === undefined ||&lt;br /&gt;
            value === null&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        let text =&lt;br /&gt;
            String(&lt;br /&gt;
                value&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 全角・半角を可能な範囲で統一&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            typeof text.normalize ===&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            text =&lt;br /&gt;
                text.normalize(&lt;br /&gt;
                    &#039;NFKC&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 大文字小文字を統一&lt;br /&gt;
         */&lt;br /&gt;
        text =&lt;br /&gt;
            text.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 連続空白を統一&lt;br /&gt;
         */&lt;br /&gt;
        text =&lt;br /&gt;
            text.replace(&lt;br /&gt;
                /\s+/g,&lt;br /&gt;
                &#039; &#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return text;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * Cargo Query&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function cargoQuery(&lt;br /&gt;
        table,&lt;br /&gt;
        fields,&lt;br /&gt;
        where&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const params = {&lt;br /&gt;
&lt;br /&gt;
            action:&lt;br /&gt;
                &#039;cargoquery&#039;,&lt;br /&gt;
&lt;br /&gt;
            tables:&lt;br /&gt;
                table,&lt;br /&gt;
&lt;br /&gt;
            fields:&lt;br /&gt;
                fields,&lt;br /&gt;
&lt;br /&gt;
            limit:&lt;br /&gt;
                500,&lt;br /&gt;
&lt;br /&gt;
            format:&lt;br /&gt;
                &#039;json&#039;&lt;br /&gt;
&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            where&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            params.where =&lt;br /&gt;
                where;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return api.get(&lt;br /&gt;
            params&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !data ||&lt;br /&gt;
                    !Array.isArray(&lt;br /&gt;
                        data.cargoquery&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    return [];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                return data.cargoquery.map(&lt;br /&gt;
                    function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            item.title ||&lt;br /&gt;
                            item&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * placement_id一覧&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    const placementIds =&lt;br /&gt;
        cards&lt;br /&gt;
            .map(&lt;br /&gt;
                function ( card ) {&lt;br /&gt;
&lt;br /&gt;
                    return String(&lt;br /&gt;
                        card.dataset&lt;br /&gt;
                            .placementId ||&lt;br /&gt;
                        &#039;&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( id ) {&lt;br /&gt;
&lt;br /&gt;
                    return (&lt;br /&gt;
                        /^\d+$/.test(&lt;br /&gt;
                            id&lt;br /&gt;
                        ) &amp;amp;&amp;amp;&lt;br /&gt;
                        id !== &#039;0&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 地図markerを持つplacementだけを&lt;br /&gt;
     * 一覧用placementIdsとは分離して管理&lt;br /&gt;
     */&lt;br /&gt;
    const mapPlacementIds =&lt;br /&gt;
        cards&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( card ) {&lt;br /&gt;
&lt;br /&gt;
                    const latitude =&lt;br /&gt;
                        String(&lt;br /&gt;
                            card.dataset&lt;br /&gt;
                                .latitude ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        ).trim();&lt;br /&gt;
&lt;br /&gt;
                    const longitude =&lt;br /&gt;
                        String(&lt;br /&gt;
                            card.dataset&lt;br /&gt;
                                .longitude ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        ).trim();&lt;br /&gt;
&lt;br /&gt;
                    return (&lt;br /&gt;
                        latitude !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                        longitude !== &#039;&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .map(&lt;br /&gt;
                function ( card ) {&lt;br /&gt;
&lt;br /&gt;
                    return String(&lt;br /&gt;
                        card.dataset&lt;br /&gt;
                            .placementId ||&lt;br /&gt;
                        &#039;&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( id ) {&lt;br /&gt;
&lt;br /&gt;
                    return (&lt;br /&gt;
                        /^\d+$/.test(&lt;br /&gt;
                            id&lt;br /&gt;
                        ) &amp;amp;&amp;amp;&lt;br /&gt;
                        id !== &#039;0&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        placementIds.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 屋台一覧 ↔ Leaflet地図連動&lt;br /&gt;
 *&lt;br /&gt;
 * placement_id を使って&lt;br /&gt;
 * markerを表示・非表示&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
const festivalMapMarkerIndex =&lt;br /&gt;
    {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 地図markerの登録が完了したか&lt;br /&gt;
 */&lt;br /&gt;
let festivalMapMarkerIndexReady =&lt;br /&gt;
    false;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * R10-5C ISSUE-07:&lt;br /&gt;
 * Festival地図の初期viewportを&lt;br /&gt;
 * marker群へ合わせたか。&lt;br /&gt;
 */&lt;br /&gt;
let festivalMapInitialViewportApplied =&lt;br /&gt;
    false;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * R10-5C7:&lt;br /&gt;
 * Maps拡張の初期center/zoom処理が&lt;br /&gt;
 * 完了した次taskでviewportを適用する。&lt;br /&gt;
 */&lt;br /&gt;
let festivalMapInitialViewportTimer =&lt;br /&gt;
    null;&lt;br /&gt;
&lt;br /&gt;
let festivalMapInitialViewportAttempts =&lt;br /&gt;
    0;&lt;br /&gt;
&lt;br /&gt;
const MAX_FESTIVAL_MAP_VIEWPORT_ATTEMPTS =&lt;br /&gt;
    40;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 地図生成前に検索された場合に備えて&lt;br /&gt;
 * 最新の表示対象を保持&lt;br /&gt;
 */&lt;br /&gt;
let pendingVisiblePlacementIds =&lt;br /&gt;
    placementIds.slice();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 地図初期化待ち&lt;br /&gt;
 */&lt;br /&gt;
let mapIndexTimer =&lt;br /&gt;
    null;&lt;br /&gt;
&lt;br /&gt;
let mapIndexAttempts =&lt;br /&gt;
    0;&lt;br /&gt;
&lt;br /&gt;
const MAX_MAP_INDEX_ATTEMPTS =&lt;br /&gt;
    40;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * marker popupから&lt;br /&gt;
 * placement_idを取得&lt;br /&gt;
 *&lt;br /&gt;
 * popup内には&lt;br /&gt;
 * #compare-placement-3&lt;br /&gt;
 * のようなリンクが存在する&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function getPlacementIdFromMapMarker(&lt;br /&gt;
    marker&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !marker ||&lt;br /&gt;
        typeof marker.getPopup !==&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const popup =&lt;br /&gt;
        marker.getPopup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !popup ||&lt;br /&gt;
        typeof popup.getContent !==&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const content =&lt;br /&gt;
        popup.getContent();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    let popupText =&lt;br /&gt;
        &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 通常はHTML文字列&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        typeof content ===&lt;br /&gt;
        &#039;string&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        popupText =&lt;br /&gt;
            content;&lt;br /&gt;
&lt;br /&gt;
    } else if (&lt;br /&gt;
        content &amp;amp;&amp;amp;&lt;br /&gt;
        typeof content.outerHTML ===&lt;br /&gt;
            &#039;string&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        popupText =&lt;br /&gt;
            content.outerHTML;&lt;br /&gt;
&lt;br /&gt;
    } else if (&lt;br /&gt;
        content &amp;amp;&amp;amp;&lt;br /&gt;
        typeof content.innerHTML ===&lt;br /&gt;
            &#039;string&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        popupText =&lt;br /&gt;
            content.innerHTML;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const match =&lt;br /&gt;
        popupText.match(&lt;br /&gt;
            /compare-placement-(\d+)/&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !match&lt;br /&gt;
    ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return match[&lt;br /&gt;
        1&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * Maps拡張のmarkerを&lt;br /&gt;
 * placement_idごとに登録&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function buildFestivalMapMarkerIndex() {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapMarkerIndexReady&lt;br /&gt;
    ) {&lt;br /&gt;
        scheduleFestivalMapInitialViewport();&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 座標付きplacementが0件なら&lt;br /&gt;
     * marker indexは0件で正常完了&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        mapPlacementIds.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        festivalMapMarkerIndexReady =&lt;br /&gt;
            true;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !Array.isArray(&lt;br /&gt;
            window.mapsLeafletList&lt;br /&gt;
        )&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const expectedIds =&lt;br /&gt;
        new Set(&lt;br /&gt;
            mapPlacementIds.map(&lt;br /&gt;
                String&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    window.mapsLeafletList.forEach(&lt;br /&gt;
        function ( jqueryMap ) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !jqueryMap ||&lt;br /&gt;
                !jqueryMap.mapContent ||&lt;br /&gt;
                !jqueryMap.mapContent&lt;br /&gt;
                    .markerLayer&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const markerLayer =&lt;br /&gt;
                jqueryMap&lt;br /&gt;
                    .mapContent&lt;br /&gt;
                    .markerLayer;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                typeof markerLayer&lt;br /&gt;
                    .getLayers !==&lt;br /&gt;
                &#039;function&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const markers =&lt;br /&gt;
                markerLayer.getLayers();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            markers.forEach(&lt;br /&gt;
                function ( marker ) {&lt;br /&gt;
&lt;br /&gt;
                    const placementId =&lt;br /&gt;
                        getPlacementIdFromMapMarker(&lt;br /&gt;
                            marker&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * この祭りページの&lt;br /&gt;
                     * placementだけ登録&lt;br /&gt;
                     */&lt;br /&gt;
                    if (&lt;br /&gt;
                        !placementId ||&lt;br /&gt;
                        !expectedIds.has(&lt;br /&gt;
                            placementId&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
festivalMapMarkerIndex[&lt;br /&gt;
    placementId&lt;br /&gt;
] = {&lt;br /&gt;
&lt;br /&gt;
    marker:&lt;br /&gt;
        marker,&lt;br /&gt;
&lt;br /&gt;
    markerLayer:&lt;br /&gt;
        markerLayer,&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * このmarkerが所属する地図DOM&lt;br /&gt;
     */&lt;br /&gt;
    mapElement:&lt;br /&gt;
        jqueryMap &amp;amp;&amp;amp;&lt;br /&gt;
        jqueryMap[&lt;br /&gt;
            0&lt;br /&gt;
        ]&lt;br /&gt;
            ? jqueryMap[&lt;br /&gt;
                0&lt;br /&gt;
            ]&lt;br /&gt;
            : null&lt;br /&gt;
&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 全placementのmarkerが&lt;br /&gt;
     * 見つかった場合だけ連動開始&lt;br /&gt;
     *&lt;br /&gt;
     * 中途半端な状態では&lt;br /&gt;
     * 地図を変更しない&lt;br /&gt;
     */&lt;br /&gt;
    festivalMapMarkerIndexReady =&lt;br /&gt;
        Array.from(&lt;br /&gt;
            expectedIds&lt;br /&gt;
        ).every(&lt;br /&gt;
            function (&lt;br /&gt;
                placementId&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                return Boolean(&lt;br /&gt;
                    festivalMapMarkerIndex[&lt;br /&gt;
                        placementId&lt;br /&gt;
                    ]&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapMarkerIndexReady&lt;br /&gt;
    ) {&lt;br /&gt;
        scheduleFestivalMapInitialViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return (&lt;br /&gt;
        festivalMapMarkerIndexReady&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * =====================================&lt;br /&gt;
 * R10-5C ISSUE-07&lt;br /&gt;
 * Festival「地図から探す」初期viewport&lt;br /&gt;
 *&lt;br /&gt;
 * 0 marker:&lt;br /&gt;
 *   現行fallbackを維持&lt;br /&gt;
 *&lt;br /&gt;
 * 1 marker:&lt;br /&gt;
 *   marker中央、zoom上限17&lt;br /&gt;
 *&lt;br /&gt;
 * 2 marker以上:&lt;br /&gt;
 *   全markerをfitBounds&lt;br /&gt;
 *   padding 32px&lt;br /&gt;
 *   maxZoom 17&lt;br /&gt;
 * =====================================&lt;br /&gt;
 */&lt;br /&gt;
function scheduleFestivalMapInitialViewport() {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapInitialViewportApplied ||&lt;br /&gt;
        !festivalMapMarkerIndexReady&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 二重timer防止。&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapInitialViewportTimer !==&lt;br /&gt;
            null&lt;br /&gt;
    ) {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Festival地図に属するmarkerから&lt;br /&gt;
     * 対象Leaflet mapを特定する。&lt;br /&gt;
     */&lt;br /&gt;
    const indexedItem =&lt;br /&gt;
        mapPlacementIds&lt;br /&gt;
            .map(&lt;br /&gt;
                String&lt;br /&gt;
            )&lt;br /&gt;
            .map(&lt;br /&gt;
                function (&lt;br /&gt;
                    placementId&lt;br /&gt;
                ) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        festivalMapMarkerIndex[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] ||&lt;br /&gt;
                        null&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .find(&lt;br /&gt;
                function ( item ) {&lt;br /&gt;
                    return Boolean(&lt;br /&gt;
                        item &amp;amp;&amp;amp;&lt;br /&gt;
                        item.marker &amp;amp;&amp;amp;&lt;br /&gt;
                        item.markerLayer &amp;amp;&amp;amp;&lt;br /&gt;
                        item.markerLayer._map&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 座標付きplacement自体が0件なら&lt;br /&gt;
     * Maps側のfallbackを正式採用して完了。&lt;br /&gt;
     *&lt;br /&gt;
     * placementが存在するのにindexedItemが&lt;br /&gt;
     * まだ取れない場合は初期化途中なので、&lt;br /&gt;
     * applied=trueにせず再試行する。&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        !indexedItem&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            mapPlacementIds.length ===&lt;br /&gt;
                0&lt;br /&gt;
        ) {&lt;br /&gt;
            festivalMapInitialViewportApplied =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        festivalMapInitialViewportAttempts +=&lt;br /&gt;
            1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            festivalMapInitialViewportAttempts &amp;gt;=&lt;br /&gt;
            MAX_FESTIVAL_MAP_VIEWPORT_ATTEMPTS&lt;br /&gt;
        ) {&lt;br /&gt;
            console.warn(&lt;br /&gt;
                &#039;祭り屋台地図：markerのLeaflet map接続を確認できなかったため、初期viewport調整を中止しました。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        festivalMapInitialViewportTimer =&lt;br /&gt;
            window.setTimeout(&lt;br /&gt;
                function () {&lt;br /&gt;
&lt;br /&gt;
                    festivalMapInitialViewportTimer =&lt;br /&gt;
                        null;&lt;br /&gt;
&lt;br /&gt;
                    scheduleFestivalMapInitialViewport();&lt;br /&gt;
                },&lt;br /&gt;
                100&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const targetMap =&lt;br /&gt;
        indexedItem&lt;br /&gt;
            .markerLayer&lt;br /&gt;
            ._map;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const mapsEntry =&lt;br /&gt;
        Array.isArray(&lt;br /&gt;
            window.mapsLeafletList&lt;br /&gt;
        )&lt;br /&gt;
            ? window.mapsLeafletList&lt;br /&gt;
                .find(&lt;br /&gt;
                    function ( entry ) {&lt;br /&gt;
                        return Boolean(&lt;br /&gt;
                            entry &amp;amp;&amp;amp;&lt;br /&gt;
                            entry.map ===&lt;br /&gt;
                                targetMap&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
                )&lt;br /&gt;
            : null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Maps setup() は doSetup() 冒頭で&lt;br /&gt;
     * ranSetup=true にした後、&lt;br /&gt;
     * centerAndZoomMap() を実行する。&lt;br /&gt;
     *&lt;br /&gt;
     * ranSetup=trueでも同一call stack中なら&lt;br /&gt;
     * Maps側のzoom=18がまだ後続するため、&lt;br /&gt;
     * 必ず次taskへ送る。&lt;br /&gt;
     *&lt;br /&gt;
     * ranSetup前なら100ms単位で待機する。&lt;br /&gt;
     */&lt;br /&gt;
    const delay =&lt;br /&gt;
        mapsEntry &amp;amp;&amp;amp;&lt;br /&gt;
        mapsEntry.ranSetup ===&lt;br /&gt;
            true&lt;br /&gt;
            ? 0&lt;br /&gt;
            : 100;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    festivalMapInitialViewportTimer =&lt;br /&gt;
        window.setTimeout(&lt;br /&gt;
            function () {&lt;br /&gt;
&lt;br /&gt;
                festivalMapInitialViewportTimer =&lt;br /&gt;
                    null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * timer実行時点でもMaps setupが&lt;br /&gt;
                 * 完了していなければ再試行。&lt;br /&gt;
                 */&lt;br /&gt;
                if (&lt;br /&gt;
                    !mapsEntry ||&lt;br /&gt;
                    mapsEntry.ranSetup !==&lt;br /&gt;
                        true&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    festivalMapInitialViewportAttempts +=&lt;br /&gt;
                        1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        festivalMapInitialViewportAttempts &amp;gt;=&lt;br /&gt;
                        MAX_FESTIVAL_MAP_VIEWPORT_ATTEMPTS&lt;br /&gt;
                    ) {&lt;br /&gt;
                        console.warn(&lt;br /&gt;
                            &#039;祭り屋台地図：Maps初期化完了を確認できなかったため、初期viewport調整を中止しました。&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    scheduleFestivalMapInitialViewport();&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    applyFestivalMapInitialViewport()&lt;br /&gt;
                ) {&lt;br /&gt;
                    festivalMapInitialViewportAttempts =&lt;br /&gt;
                        0;&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                festivalMapInitialViewportAttempts +=&lt;br /&gt;
                    1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    festivalMapInitialViewportAttempts &amp;gt;=&lt;br /&gt;
                    MAX_FESTIVAL_MAP_VIEWPORT_ATTEMPTS&lt;br /&gt;
                ) {&lt;br /&gt;
                    console.warn(&lt;br /&gt;
                        &#039;祭り屋台地図：初期viewportを適用できなかったため、再試行を中止しました。&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                scheduleFestivalMapInitialViewport();&lt;br /&gt;
            },&lt;br /&gt;
            delay&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function applyFestivalMapInitialViewport() {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapInitialViewportApplied ||&lt;br /&gt;
        !festivalMapMarkerIndexReady&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        typeof L === &#039;undefined&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const items =&lt;br /&gt;
        mapPlacementIds&lt;br /&gt;
            .map(&lt;br /&gt;
                String&lt;br /&gt;
            )&lt;br /&gt;
            .map(&lt;br /&gt;
                function (&lt;br /&gt;
                    placementId&lt;br /&gt;
                ) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        festivalMapMarkerIndex[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] ||&lt;br /&gt;
                        null&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( item ) {&lt;br /&gt;
                    return Boolean(&lt;br /&gt;
                        item &amp;amp;&amp;amp;&lt;br /&gt;
                        item.marker &amp;amp;&amp;amp;&lt;br /&gt;
                        typeof item.marker&lt;br /&gt;
                            .getLatLng ===&lt;br /&gt;
                            &#039;function&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                        item.markerLayer&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * mapPlacementIdsが存在する状態で&lt;br /&gt;
     * itemsが0件なのは初期化途中。&lt;br /&gt;
     *&lt;br /&gt;
     * applied=trueにはせず、&lt;br /&gt;
     * schedulerへfalseを返して再試行させる。&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        items.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const map =&lt;br /&gt;
        items[&lt;br /&gt;
            0&lt;br /&gt;
        ].markerLayer &amp;amp;&amp;amp;&lt;br /&gt;
        items[&lt;br /&gt;
            0&lt;br /&gt;
        ].markerLayer._map&lt;br /&gt;
            ? items[&lt;br /&gt;
                0&lt;br /&gt;
            ].markerLayer._map&lt;br /&gt;
            : null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !map ||&lt;br /&gt;
        typeof map.setView !==&lt;br /&gt;
            &#039;function&#039; ||&lt;br /&gt;
        typeof map.fitBounds !==&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 同一Festival地図に属するmarkerだけを&lt;br /&gt;
     * viewport計算へ使用。&lt;br /&gt;
     */&lt;br /&gt;
    const latLngs =&lt;br /&gt;
        items&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( item ) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        item.markerLayer &amp;amp;&amp;amp;&lt;br /&gt;
                        item.markerLayer._map ===&lt;br /&gt;
                            map&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .map(&lt;br /&gt;
                function ( item ) {&lt;br /&gt;
                    return item.marker&lt;br /&gt;
                        .getLatLng();&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( latlng ) {&lt;br /&gt;
                    return Boolean(&lt;br /&gt;
                        latlng &amp;amp;&amp;amp;&lt;br /&gt;
                        Number.isFinite(&lt;br /&gt;
                            Number(&lt;br /&gt;
                                latlng.lat&lt;br /&gt;
                            )&lt;br /&gt;
                        ) &amp;amp;&amp;amp;&lt;br /&gt;
                        Number.isFinite(&lt;br /&gt;
                            Number(&lt;br /&gt;
                                latlng.lng&lt;br /&gt;
                            )&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        latLngs.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        latLngs.length === 1&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        map.setView(&lt;br /&gt;
            latLngs[&lt;br /&gt;
                0&lt;br /&gt;
            ],&lt;br /&gt;
            17,&lt;br /&gt;
            {&lt;br /&gt;
                animate:&lt;br /&gt;
                    false&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
&lt;br /&gt;
        map.fitBounds(&lt;br /&gt;
            L.latLngBounds(&lt;br /&gt;
                latLngs&lt;br /&gt;
            ),&lt;br /&gt;
            {&lt;br /&gt;
                padding:&lt;br /&gt;
                    [&lt;br /&gt;
                        32,&lt;br /&gt;
                        32&lt;br /&gt;
                    ],&lt;br /&gt;
&lt;br /&gt;
                maxZoom:&lt;br /&gt;
                    17,&lt;br /&gt;
&lt;br /&gt;
                animate:&lt;br /&gt;
                    false&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    festivalMapInitialViewportApplied =&lt;br /&gt;
        true;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * marker表示状態を変更&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function applyMapMarkerFilter(&lt;br /&gt;
    visiblePlacementIds&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const visibleIds =&lt;br /&gt;
        new Set(&lt;br /&gt;
            visiblePlacementIds.map(&lt;br /&gt;
                String&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    Object.keys(&lt;br /&gt;
        festivalMapMarkerIndex&lt;br /&gt;
    ).forEach(&lt;br /&gt;
        function ( placementId ) {&lt;br /&gt;
&lt;br /&gt;
            const item =&lt;br /&gt;
                festivalMapMarkerIndex[&lt;br /&gt;
                    placementId&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !item ||&lt;br /&gt;
                !item.marker ||&lt;br /&gt;
                !item.markerLayer&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const marker =&lt;br /&gt;
                item.marker;&lt;br /&gt;
&lt;br /&gt;
            const markerLayer =&lt;br /&gt;
                item.markerLayer;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * markerが現在表示されているか&lt;br /&gt;
             */&lt;br /&gt;
            const isShown =&lt;br /&gt;
                typeof markerLayer&lt;br /&gt;
                    .hasLayer ===&lt;br /&gt;
                    &#039;function&#039;&lt;br /&gt;
                    ? markerLayer.hasLayer(&lt;br /&gt;
                        marker&lt;br /&gt;
                    )&lt;br /&gt;
                    : true;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 表示対象&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                visibleIds.has(&lt;br /&gt;
                    placementId&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !isShown &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof markerLayer&lt;br /&gt;
                        .addLayer ===&lt;br /&gt;
                        &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    markerLayer.addLayer(&lt;br /&gt;
                        marker&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 非表示対象&lt;br /&gt;
             */&lt;br /&gt;
            } else {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    isShown &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof markerLayer&lt;br /&gt;
                        .removeLayer ===&lt;br /&gt;
                        &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    markerLayer.removeLayer(&lt;br /&gt;
                        marker&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 地図初期化待ち&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function scheduleMapMarkerIndex() {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        festivalMapMarkerIndexReady&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        applyMapMarkerFilter(&lt;br /&gt;
            pendingVisiblePlacementIds&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 二重タイマー防止&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        mapIndexTimer !== null&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function tryIndex() {&lt;br /&gt;
&lt;br /&gt;
        mapIndexTimer =&lt;br /&gt;
            null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            buildFestivalMapMarkerIndex()&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 地図準備完了後、&lt;br /&gt;
             * 最新の絞り込み状態を反映&lt;br /&gt;
             */&lt;br /&gt;
            applyMapMarkerFilter(&lt;br /&gt;
                pendingVisiblePlacementIds&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        mapIndexAttempts +=&lt;br /&gt;
            1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            mapIndexAttempts &amp;gt;=&lt;br /&gt;
            MAX_MAP_INDEX_ATTEMPTS&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            console.warn(&lt;br /&gt;
                &#039;祭り屋台地図：placement_idとmarkerを対応付けできませんでした。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        mapIndexTimer =&lt;br /&gt;
            window.setTimeout(&lt;br /&gt;
                tryIndex,&lt;br /&gt;
                100&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    tryIndex();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 一覧の検索結果を&lt;br /&gt;
 * 地図へ反映&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function syncMapMarkers(&lt;br /&gt;
    visiblePlacementIds&lt;br /&gt;
) {&lt;br /&gt;
	&lt;br /&gt;
	    pendingVisiblePlacementIds =&lt;br /&gt;
        visiblePlacementIds&lt;br /&gt;
            .map(&lt;br /&gt;
                String&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        buildFestivalMapMarkerIndex()&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        applyMapMarkerFilter(&lt;br /&gt;
            pendingVisiblePlacementIds&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Maps側がまだ初期化されていれば待つ&lt;br /&gt;
     */&lt;br /&gt;
    scheduleMapMarkerIndex();&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
	&lt;br /&gt;
	/* =====================================&lt;br /&gt;
 * placement_idのmarkerを開く&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function openPlacementOnMap(&lt;br /&gt;
    placementId&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const id =&lt;br /&gt;
        String(&lt;br /&gt;
            placementId ||&lt;br /&gt;
            &#039;&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !/^\d+$/.test(&lt;br /&gt;
            id&lt;br /&gt;
        ) ||&lt;br /&gt;
        id === &#039;0&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * marker index未完成なら&lt;br /&gt;
     * 一度構築を試す&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        !festivalMapMarkerIndex[&lt;br /&gt;
            id&lt;br /&gt;
        ]&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        buildFestivalMapMarkerIndex();&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const item =&lt;br /&gt;
        festivalMapMarkerIndex[&lt;br /&gt;
            id&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !item ||&lt;br /&gt;
        !item.marker&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        console.warn(&lt;br /&gt;
            &#039;地図markerが見つかりません:&#039;,&lt;br /&gt;
            id&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const marker =&lt;br /&gt;
        item.marker;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 万一markerが非表示なら&lt;br /&gt;
     * 地図へ戻す&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        item.markerLayer &amp;amp;&amp;amp;&lt;br /&gt;
        typeof item.markerLayer&lt;br /&gt;
            .hasLayer ===&lt;br /&gt;
            &#039;function&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !item.markerLayer.hasLayer(&lt;br /&gt;
            marker&lt;br /&gt;
        ) &amp;amp;&amp;amp;&lt;br /&gt;
        typeof item.markerLayer&lt;br /&gt;
            .addLayer ===&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        item.markerLayer.addLayer(&lt;br /&gt;
            marker&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 地図までスクロール&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        item.mapElement &amp;amp;&amp;amp;&lt;br /&gt;
        typeof item.mapElement&lt;br /&gt;
            .scrollIntoView ===&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        item.mapElement.scrollIntoView(&lt;br /&gt;
            {&lt;br /&gt;
                behavior:&lt;br /&gt;
                    &#039;smooth&#039;,&lt;br /&gt;
&lt;br /&gt;
                block:&lt;br /&gt;
                    &#039;center&#039;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 少し待ってpopupを開く&lt;br /&gt;
     */&lt;br /&gt;
    window.setTimeout(&lt;br /&gt;
        function () {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                typeof marker.openPopup ===&lt;br /&gt;
                &#039;function&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                marker.openPopup();&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        },&lt;br /&gt;
        300&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 各屋台カード&lt;br /&gt;
 * 「地図で見る」ボタン生成&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function createMapViewButtons() {&lt;br /&gt;
&lt;br /&gt;
    cards.forEach(&lt;br /&gt;
        function ( card ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 二重生成防止&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                card.querySelector(&lt;br /&gt;
                    &#039;.festival-stall-map-view&#039;&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const placementId =&lt;br /&gt;
                String(&lt;br /&gt;
                    card.dataset&lt;br /&gt;
                        .placementId ||&lt;br /&gt;
                    &#039;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !/^\d+$/.test(&lt;br /&gt;
                    placementId&lt;br /&gt;
                ) ||&lt;br /&gt;
                placementId === &#039;0&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 座標なしplacementには&lt;br /&gt;
             * 地図ボタンを表示しない&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                !mapPlacementIds.includes(&lt;br /&gt;
                    placementId&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const wrapper =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;div&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            wrapper.className =&lt;br /&gt;
                &#039;festival-stall-map-view&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const button =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;button&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            button.type =&lt;br /&gt;
                &#039;button&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.className =&lt;br /&gt;
                &#039;festival-stall-map-view-button&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.dataset.placementId =&lt;br /&gt;
                placementId;&lt;br /&gt;
&lt;br /&gt;
            button.textContent =&lt;br /&gt;
                &#039;地図で見る&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            button.setAttribute(&lt;br /&gt;
                &#039;aria-label&#039;,&lt;br /&gt;
                &#039;この屋台を地図で見る&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            wrapper.appendChild(&lt;br /&gt;
                button&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 比較ボタンの近くへ配置&lt;br /&gt;
             */&lt;br /&gt;
            const compareControl =&lt;br /&gt;
                card.querySelector(&lt;br /&gt;
                    &#039;.stall-compare-control&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                compareControl &amp;amp;&amp;amp;&lt;br /&gt;
                compareControl.parentNode&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                compareControl.parentNode&lt;br /&gt;
                    .insertBefore(&lt;br /&gt;
                        wrapper,&lt;br /&gt;
                        compareControl&lt;br /&gt;
                            .nextSibling&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
            } else {&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 比較ボタンが見つからない場合は&lt;br /&gt;
                 * カード末尾&lt;br /&gt;
                 */&lt;br /&gt;
                card.appendChild(&lt;br /&gt;
                    wrapper&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 「地図で見る」クリック&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
document.addEventListener(&lt;br /&gt;
    &#039;click&#039;,&lt;br /&gt;
    function ( event ) {&lt;br /&gt;
&lt;br /&gt;
        const button =&lt;br /&gt;
            event.target.closest(&lt;br /&gt;
                &#039;.festival-stall-map-view-button&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !button&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const placementId =&lt;br /&gt;
            String(&lt;br /&gt;
                button.dataset&lt;br /&gt;
                    .placementId ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const opened =&lt;br /&gt;
            openPlacementOnMap(&lt;br /&gt;
                placementId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Maps初期化前だった場合&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !opened&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            scheduleMapMarkerIndex();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            button.disabled =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            button.textContent =&lt;br /&gt;
                &#039;地図を準備中…&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            window.setTimeout(&lt;br /&gt;
                function () {&lt;br /&gt;
&lt;br /&gt;
                    button.disabled =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    button.textContent =&lt;br /&gt;
                        &#039;地図で見る&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    openPlacementOnMap(&lt;br /&gt;
                        placementId&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                },&lt;br /&gt;
                500&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 検索UI&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    const searchBox =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;div&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    searchBox.className =&lt;br /&gt;
        &#039;festival-stall-search&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const label =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;label&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    label.className =&lt;br /&gt;
        &#039;festival-stall-search-label&#039;;&lt;br /&gt;
&lt;br /&gt;
    label.textContent =&lt;br /&gt;
        &#039;屋台を検索&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const input =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;input&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    input.type =&lt;br /&gt;
        &#039;search&#039;;&lt;br /&gt;
&lt;br /&gt;
    input.className =&lt;br /&gt;
        &#039;festival-stall-search-input&#039;;&lt;br /&gt;
&lt;br /&gt;
    input.placeholder =&lt;br /&gt;
        &#039;屋台名・商品名を入力&#039;;&lt;br /&gt;
&lt;br /&gt;
    input.setAttribute(&lt;br /&gt;
        &#039;autocomplete&#039;,&lt;br /&gt;
        &#039;off&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    input.setAttribute(&lt;br /&gt;
        &#039;aria-label&#039;,&lt;br /&gt;
        &#039;屋台名または商品名で検索&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * フィルターselect&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function createFilterSelect(&lt;br /&gt;
    labelText,&lt;br /&gt;
    className,&lt;br /&gt;
    allText&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const wrapper =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;label&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    wrapper.className =&lt;br /&gt;
        &#039;festival-stall-filter&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const title =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;span&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    title.className =&lt;br /&gt;
        &#039;festival-stall-filter-label&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent =&lt;br /&gt;
        labelText;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const select =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;select&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    select.className =&lt;br /&gt;
        &#039;festival-stall-filter-select &#039; +&lt;br /&gt;
        className;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const allOption =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;option&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    allOption.value =&lt;br /&gt;
        &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    allOption.textContent =&lt;br /&gt;
        allText;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    select.appendChild(&lt;br /&gt;
        allOption&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    wrapper.appendChild(&lt;br /&gt;
        title&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    wrapper.appendChild(&lt;br /&gt;
        select&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        wrapper:&lt;br /&gt;
            wrapper,&lt;br /&gt;
&lt;br /&gt;
        select:&lt;br /&gt;
            select&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * カテゴリ&lt;br /&gt;
 */&lt;br /&gt;
const categoryFilter =&lt;br /&gt;
    createFilterSelect(&lt;br /&gt;
        &#039;カテゴリ&#039;,&lt;br /&gt;
        &#039;festival-stall-category-filter&#039;,&lt;br /&gt;
        &#039;すべて&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 会場&lt;br /&gt;
 */&lt;br /&gt;
const venueFilter =&lt;br /&gt;
    createFilterSelect(&lt;br /&gt;
        &#039;会場&#039;,&lt;br /&gt;
        &#039;festival-stall-venue-filter&#039;,&lt;br /&gt;
        &#039;すべて&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const categorySelect =&lt;br /&gt;
    categoryFilter.select;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const venueSelect =&lt;br /&gt;
    venueFilter.select;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * フィルター行&lt;br /&gt;
 */&lt;br /&gt;
const filterRow =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
filterRow.className =&lt;br /&gt;
    &#039;festival-stall-search-filters&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
filterRow.appendChild(&lt;br /&gt;
    categoryFilter.wrapper&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
filterRow.appendChild(&lt;br /&gt;
    venueFilter.wrapper&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 絞り込みリセット&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
const resetButton =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;button&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
resetButton.type =&lt;br /&gt;
    &#039;button&#039;;&lt;br /&gt;
&lt;br /&gt;
resetButton.className =&lt;br /&gt;
    &#039;festival-stall-search-reset&#039;;&lt;br /&gt;
&lt;br /&gt;
resetButton.textContent =&lt;br /&gt;
    &#039;絞り込みをリセット&#039;;&lt;br /&gt;
&lt;br /&gt;
resetButton.setAttribute(&lt;br /&gt;
    &#039;aria-label&#039;,&lt;br /&gt;
    &#039;屋台の検索条件をすべてリセット&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
resetButton.disabled =&lt;br /&gt;
    true;&lt;br /&gt;
&lt;br /&gt;
    const count =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;div&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    count.className =&lt;br /&gt;
        &#039;festival-stall-search-count&#039;;&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 検索結果0件メッセージ&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
const noResults =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
noResults.className =&lt;br /&gt;
    &#039;festival-stall-search-empty&#039;;&lt;br /&gt;
&lt;br /&gt;
noResults.textContent =&lt;br /&gt;
    &#039;条件に一致する屋台はありません。検索条件を変更してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
noResults.hidden =&lt;br /&gt;
    true;&lt;br /&gt;
&lt;br /&gt;
noResults.setAttribute(&lt;br /&gt;
    &#039;role&#039;,&lt;br /&gt;
    &#039;status&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
    label.appendChild(&lt;br /&gt;
        input&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
searchBox.appendChild(&lt;br /&gt;
    label&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
searchBox.appendChild(&lt;br /&gt;
    filterRow&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * リセット&lt;br /&gt;
 */&lt;br /&gt;
searchBox.appendChild(&lt;br /&gt;
    resetButton&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
searchBox.appendChild(&lt;br /&gt;
    count&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
searchBox.appendChild(&lt;br /&gt;
    noResults&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 最初の屋台カードの直前に表示&lt;br /&gt;
     */&lt;br /&gt;
/*&lt;br /&gt;
 * 検索UIの表示位置&lt;br /&gt;
 */&lt;br /&gt;
const searchAnchor =&lt;br /&gt;
    document.getElementById(&lt;br /&gt;
        &#039;festival-stall-search-anchor&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
    searchAnchor&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    searchAnchor.appendChild(&lt;br /&gt;
        searchBox&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
} else {&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 古いテンプレート等への&lt;br /&gt;
     * フォールバック&lt;br /&gt;
     */&lt;br /&gt;
    cards[&lt;br /&gt;
        0&lt;br /&gt;
    ].parentNode.insertBefore(&lt;br /&gt;
        searchBox,&lt;br /&gt;
        cards[&lt;br /&gt;
            0&lt;br /&gt;
        ]&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * カードごとの検索文字列&lt;br /&gt;
     *&lt;br /&gt;
     * 最初はカード本文だけ&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
const searchIndex = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * select候補&lt;br /&gt;
 */&lt;br /&gt;
const categoryOptions =&lt;br /&gt;
    new Map();&lt;br /&gt;
&lt;br /&gt;
const venueOptions =&lt;br /&gt;
    new Map();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
cards.forEach(&lt;br /&gt;
    function ( card ) {&lt;br /&gt;
&lt;br /&gt;
        const placementId =&lt;br /&gt;
            String(&lt;br /&gt;
                card.dataset&lt;br /&gt;
                    .placementId ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const category =&lt;br /&gt;
            String(&lt;br /&gt;
                card.dataset&lt;br /&gt;
                    .category ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const venueName =&lt;br /&gt;
            String(&lt;br /&gt;
                card.dataset&lt;br /&gt;
                    .venueName ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const normalizedCategory =&lt;br /&gt;
            normalizeSearchText(&lt;br /&gt;
                category&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const normalizedVenue =&lt;br /&gt;
            normalizeSearchText(&lt;br /&gt;
                venueName&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * placementごとの検索情報&lt;br /&gt;
         */&lt;br /&gt;
        searchIndex[&lt;br /&gt;
            placementId&lt;br /&gt;
        ] = {&lt;br /&gt;
&lt;br /&gt;
            text:&lt;br /&gt;
                normalizeSearchText(&lt;br /&gt;
                    card.textContent&lt;br /&gt;
                ),&lt;br /&gt;
&lt;br /&gt;
            category:&lt;br /&gt;
                normalizedCategory,&lt;br /&gt;
&lt;br /&gt;
            venueName:&lt;br /&gt;
                normalizedVenue&lt;br /&gt;
&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * カテゴリselect候補&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            normalizedCategory &amp;amp;&amp;amp;&lt;br /&gt;
            !categoryOptions.has(&lt;br /&gt;
                normalizedCategory&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            categoryOptions.set(&lt;br /&gt;
                normalizedCategory,&lt;br /&gt;
                category&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 会場select候補&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            normalizedVenue &amp;amp;&amp;amp;&lt;br /&gt;
            !venueOptions.has(&lt;br /&gt;
                normalizedVenue&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            venueOptions.set(&lt;br /&gt;
                normalizedVenue,&lt;br /&gt;
                venueName&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * select option生成&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function fillFilterOptions(&lt;br /&gt;
    select,&lt;br /&gt;
    optionMap&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const options =&lt;br /&gt;
        Array.from(&lt;br /&gt;
            optionMap.entries()&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 表示名で並び替え&lt;br /&gt;
     */&lt;br /&gt;
    options.sort(&lt;br /&gt;
        function ( a, b ) {&lt;br /&gt;
&lt;br /&gt;
            return a[&lt;br /&gt;
                1&lt;br /&gt;
            ].localeCompare(&lt;br /&gt;
                b[&lt;br /&gt;
                    1&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;ja&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    options.forEach(&lt;br /&gt;
        function ( optionData ) {&lt;br /&gt;
&lt;br /&gt;
            const value =&lt;br /&gt;
                optionData[&lt;br /&gt;
                    0&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
            const label =&lt;br /&gt;
                optionData[&lt;br /&gt;
                    1&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const option =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;option&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            option.value =&lt;br /&gt;
                value;&lt;br /&gt;
&lt;br /&gt;
            option.textContent =&lt;br /&gt;
                label;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            select.appendChild(&lt;br /&gt;
                option&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
fillFilterOptions(&lt;br /&gt;
    categorySelect,&lt;br /&gt;
    categoryOptions&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
fillFilterOptions(&lt;br /&gt;
    venueSelect,&lt;br /&gt;
    venueOptions&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 件数表示&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function updateCount(&lt;br /&gt;
        visible&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        count.textContent =&lt;br /&gt;
            &#039;表示：&#039; +&lt;br /&gt;
            visible +&lt;br /&gt;
            &#039; / &#039; +&lt;br /&gt;
            cards.length +&lt;br /&gt;
            &#039;件&#039;;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    updateCount(&lt;br /&gt;
        cards.length&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 検索実行&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function applySearch() {&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * フリーワード&lt;br /&gt;
     */&lt;br /&gt;
    const keyword =&lt;br /&gt;
        normalizeSearchText(&lt;br /&gt;
            input.value&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * カテゴリ&lt;br /&gt;
     */&lt;br /&gt;
    const selectedCategory =&lt;br /&gt;
        categorySelect.value;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 会場&lt;br /&gt;
     */&lt;br /&gt;
    const selectedVenue =&lt;br /&gt;
        venueSelect.value;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
let visible =&lt;br /&gt;
    0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 地図に残すplacement_id&lt;br /&gt;
 */&lt;br /&gt;
const visiblePlacementIds =&lt;br /&gt;
    [];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
cards.forEach(&lt;br /&gt;
        function ( card ) {&lt;br /&gt;
&lt;br /&gt;
            const placementId =&lt;br /&gt;
                String(&lt;br /&gt;
                    card.dataset&lt;br /&gt;
                        .placementId ||&lt;br /&gt;
                    &#039;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const index =&lt;br /&gt;
                searchIndex[&lt;br /&gt;
                    placementId&lt;br /&gt;
                ] || {&lt;br /&gt;
&lt;br /&gt;
                    text:&lt;br /&gt;
                        &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
                    category:&lt;br /&gt;
                        &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
                    venueName:&lt;br /&gt;
                        &#039;&#039;&lt;br /&gt;
&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * フリーワード&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const keywordMatched =&lt;br /&gt;
                !keyword ||&lt;br /&gt;
                index.text.includes(&lt;br /&gt;
                    keyword&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * カテゴリ&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const categoryMatched =&lt;br /&gt;
                !selectedCategory ||&lt;br /&gt;
                index.category ===&lt;br /&gt;
                    selectedCategory;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 会場&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const venueMatched =&lt;br /&gt;
                !selectedVenue ||&lt;br /&gt;
                index.venueName ===&lt;br /&gt;
                    selectedVenue;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * AND条件&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const matched =&lt;br /&gt;
                keywordMatched &amp;amp;&amp;amp;&lt;br /&gt;
                categoryMatched &amp;amp;&amp;amp;&lt;br /&gt;
                venueMatched;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
    matched&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    card.style.display =&lt;br /&gt;
        &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    visible +=&lt;br /&gt;
        1;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 地図にも残す&lt;br /&gt;
     */&lt;br /&gt;
    visiblePlacementIds.push(&lt;br /&gt;
        placementId&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
} else {&lt;br /&gt;
&lt;br /&gt;
                card.style.display =&lt;br /&gt;
                    &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
updateCount(&lt;br /&gt;
    visible&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 0件メッセージ&lt;br /&gt;
 */&lt;br /&gt;
noResults.hidden =&lt;br /&gt;
    visible !== 0;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 検索条件が1つでもあれば&lt;br /&gt;
 * リセットボタンを有効化&lt;br /&gt;
 */&lt;br /&gt;
resetButton.disabled =&lt;br /&gt;
    (&lt;br /&gt;
        normalizeSearchText(&lt;br /&gt;
            input.value&lt;br /&gt;
        ) === &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        categorySelect.value === &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        venueSelect.value === &#039;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 地図を一覧と同期&lt;br /&gt;
 */&lt;br /&gt;
syncMapMarkers(&lt;br /&gt;
    visiblePlacementIds&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 各カードへ&lt;br /&gt;
 * 地図で見るボタン&lt;br /&gt;
 */&lt;br /&gt;
createMapViewButtons();&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 初期状態&lt;br /&gt;
 *&lt;br /&gt;
 * 最初は全placementを表示&lt;br /&gt;
 */&lt;br /&gt;
syncMapMarkers(&lt;br /&gt;
    placementIds&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    input.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        applySearch&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
categorySelect.addEventListener(&lt;br /&gt;
    &#039;change&#039;,&lt;br /&gt;
    applySearch&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
venueSelect.addEventListener(&lt;br /&gt;
    &#039;change&#039;,&lt;br /&gt;
    applySearch&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 絞り込みをすべてリセット&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
resetButton.addEventListener(&lt;br /&gt;
    &#039;click&#039;,&lt;br /&gt;
    function () {&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * フリーワード&lt;br /&gt;
         */&lt;br /&gt;
        input.value =&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * カテゴリ&lt;br /&gt;
         */&lt;br /&gt;
        categorySelect.value =&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 会場&lt;br /&gt;
         */&lt;br /&gt;
        venueSelect.value =&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 一覧・件数・0件表示・&lt;br /&gt;
         * 地図markerをすべて再計算&lt;br /&gt;
         */&lt;br /&gt;
        applySearch();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 続けて検索しやすくする&lt;br /&gt;
         */&lt;br /&gt;
        input.focus();&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * Placement → Offering取得&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    cargoQuery(&lt;br /&gt;
&lt;br /&gt;
        &#039;FestivalStallMenuOfferings&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;placement_id=placement_id,&#039; +&lt;br /&gt;
        &#039;menu_item_id=menu_item_id&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;placement_id IN (&#039; +&lt;br /&gt;
        placementIds.join(&lt;br /&gt;
            &#039;,&#039;&lt;br /&gt;
        ) +&lt;br /&gt;
        &#039;)&#039;&lt;br /&gt;
&lt;br /&gt;
    ).then(&lt;br /&gt;
        function ( offerings ) {&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const menuItemIds =&lt;br /&gt;
                [&lt;br /&gt;
                    ...new Set(&lt;br /&gt;
                        offerings&lt;br /&gt;
                            .map(&lt;br /&gt;
                                function (&lt;br /&gt;
                                    offering&lt;br /&gt;
                                ) {&lt;br /&gt;
&lt;br /&gt;
                                    return String(&lt;br /&gt;
                                        offering&lt;br /&gt;
                                            .menu_item_id ||&lt;br /&gt;
                                        &#039;&#039;&lt;br /&gt;
                                    );&lt;br /&gt;
&lt;br /&gt;
                                }&lt;br /&gt;
                            )&lt;br /&gt;
                            .filter(&lt;br /&gt;
                                function ( id ) {&lt;br /&gt;
&lt;br /&gt;
                                    return /^\d+$/.test(&lt;br /&gt;
                                        id&lt;br /&gt;
                                    );&lt;br /&gt;
&lt;br /&gt;
                                }&lt;br /&gt;
                            )&lt;br /&gt;
                    )&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * メニューが1件も無い&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                menuItemIds.length === 0&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                return {&lt;br /&gt;
                    offerings:&lt;br /&gt;
                        offerings,&lt;br /&gt;
&lt;br /&gt;
                    menus:&lt;br /&gt;
                        []&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =================================&lt;br /&gt;
             * MenuItem名取得&lt;br /&gt;
             * ================================= */&lt;br /&gt;
&lt;br /&gt;
            return cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                &#039;StallMenuItems&#039;,&lt;br /&gt;
&lt;br /&gt;
                &#039;menu_item_id=menu_item_id,&#039; +&lt;br /&gt;
                &#039;name=menu_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                &#039;menu_item_id IN (&#039; +&lt;br /&gt;
                menuItemIds.join(&lt;br /&gt;
                    &#039;,&#039;&lt;br /&gt;
                ) +&lt;br /&gt;
                &#039;)&#039;&lt;br /&gt;
&lt;br /&gt;
            ).then(&lt;br /&gt;
                function ( menus ) {&lt;br /&gt;
&lt;br /&gt;
                    return {&lt;br /&gt;
&lt;br /&gt;
                        offerings:&lt;br /&gt;
                            offerings,&lt;br /&gt;
&lt;br /&gt;
                        menus:&lt;br /&gt;
                            menus&lt;br /&gt;
&lt;br /&gt;
                    };&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    ).then(&lt;br /&gt;
        function ( data ) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !data&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =================================&lt;br /&gt;
             * menu_item_id → 商品名&lt;br /&gt;
             * ================================= */&lt;br /&gt;
&lt;br /&gt;
            const menuNameMap =&lt;br /&gt;
                {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            data.menus.forEach(&lt;br /&gt;
                function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                    menuNameMap[&lt;br /&gt;
                        String(&lt;br /&gt;
                            menu.menu_item_id&lt;br /&gt;
                        )&lt;br /&gt;
                    ] =&lt;br /&gt;
                        menu.menu_name ||&lt;br /&gt;
                        &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =================================&lt;br /&gt;
             * placement_id → 商品名[]&lt;br /&gt;
             * ================================= */&lt;br /&gt;
&lt;br /&gt;
            const placementMenus =&lt;br /&gt;
                {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            data.offerings.forEach(&lt;br /&gt;
                function ( offering ) {&lt;br /&gt;
&lt;br /&gt;
                    const placementId =&lt;br /&gt;
                        String(&lt;br /&gt;
                            offering&lt;br /&gt;
                                .placement_id ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    const menuItemId =&lt;br /&gt;
                        String(&lt;br /&gt;
                            offering&lt;br /&gt;
                                .menu_item_id ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    const menuName =&lt;br /&gt;
                        menuNameMap[&lt;br /&gt;
                            menuItemId&lt;br /&gt;
                        ] || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        !menuName&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        !placementMenus[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ]&lt;br /&gt;
                    ) {&lt;br /&gt;
&lt;br /&gt;
                        placementMenus[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] = [];&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    placementMenus[&lt;br /&gt;
                        placementId&lt;br /&gt;
                    ].push(&lt;br /&gt;
                        menuName&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =================================&lt;br /&gt;
             * 商品名を検索インデックスへ追加&lt;br /&gt;
             * ================================= */&lt;br /&gt;
&lt;br /&gt;
            cards.forEach(&lt;br /&gt;
                function ( card ) {&lt;br /&gt;
&lt;br /&gt;
                    const placementId =&lt;br /&gt;
                        String(&lt;br /&gt;
                            card.dataset&lt;br /&gt;
                                .placementId ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    const menuNames =&lt;br /&gt;
                        placementMenus[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ] || [];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
    searchIndex[&lt;br /&gt;
        placementId&lt;br /&gt;
    ]&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    searchIndex[&lt;br /&gt;
        placementId&lt;br /&gt;
    ].text =&lt;br /&gt;
        normalizeSearchText(&lt;br /&gt;
            (&lt;br /&gt;
                searchIndex[&lt;br /&gt;
                    placementId&lt;br /&gt;
                ].text ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            ) +&lt;br /&gt;
            &#039; &#039; +&lt;br /&gt;
            menuNames.join(&lt;br /&gt;
                &#039; &#039;&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 商品データ取得後、&lt;br /&gt;
             * 入力済み検索を再判定&lt;br /&gt;
             */&lt;br /&gt;
            applySearch();&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    ).catch(&lt;br /&gt;
        function ( error ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 商品データ取得に失敗しても&lt;br /&gt;
             * 屋台名検索は使えるようにする&lt;br /&gt;
             */&lt;br /&gt;
            console.error(&lt;br /&gt;
                &#039;屋台商品検索データ取得エラー:&#039;,&lt;br /&gt;
                error&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
/* ========================================&lt;br /&gt;
 * 屋台比較ページ&lt;br /&gt;
 * placement_id 正式版&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
mw.loader.using( [&lt;br /&gt;
    &#039;mediawiki.storage&#039;,&lt;br /&gt;
    &#039;mediawiki.api&#039;,&lt;br /&gt;
    &#039;mediawiki.util&#039;&lt;br /&gt;
] ).then( function () {&lt;br /&gt;
&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const compareRoot =&lt;br /&gt;
        document.getElementById(&lt;br /&gt;
            &#039;stall-compare-page&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 屋台比較ページ以外では終了&lt;br /&gt;
     */&lt;br /&gt;
    if ( !compareRoot ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const STORAGE_KEY =&lt;br /&gt;
        &#039;matsuriWikiComparePlacements&#039;;&lt;br /&gt;
&lt;br /&gt;
    const MIN_COMPARE = 2;&lt;br /&gt;
    const MAX_COMPARE = 4;&lt;br /&gt;
&lt;br /&gt;
    const api =&lt;br /&gt;
        new mw.Api();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * localStorage&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function getPlacementIds() {&lt;br /&gt;
&lt;br /&gt;
        const raw =&lt;br /&gt;
            mw.storage.get(&lt;br /&gt;
                STORAGE_KEY&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( !raw ) {&lt;br /&gt;
            return [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
&lt;br /&gt;
            const ids =&lt;br /&gt;
                JSON.parse(&lt;br /&gt;
                    raw&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !Array.isArray(&lt;br /&gt;
                    ids&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return [];&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            return [ ...new Set(&lt;br /&gt;
                ids&lt;br /&gt;
                    .map( String )&lt;br /&gt;
                    .filter(&lt;br /&gt;
                        function ( id ) {&lt;br /&gt;
                            return /^\d+$/.test(&lt;br /&gt;
                                id&lt;br /&gt;
                            );&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
            ) ].slice(&lt;br /&gt;
                0,&lt;br /&gt;
                MAX_COMPARE&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        } catch ( e ) {&lt;br /&gt;
&lt;br /&gt;
            return [];&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * Cargo&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function cargoQuery(&lt;br /&gt;
        table,&lt;br /&gt;
        fields,&lt;br /&gt;
        where,&lt;br /&gt;
        limit&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const params = {&lt;br /&gt;
&lt;br /&gt;
            action: &#039;cargoquery&#039;,&lt;br /&gt;
&lt;br /&gt;
            tables: table,&lt;br /&gt;
&lt;br /&gt;
            fields: fields,&lt;br /&gt;
&lt;br /&gt;
            limit: limit || 100,&lt;br /&gt;
&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( where ) {&lt;br /&gt;
            params.where = where;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return api.get(&lt;br /&gt;
            params&lt;br /&gt;
        ).then(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !data ||&lt;br /&gt;
                    !Array.isArray(&lt;br /&gt;
                        data.cargoquery&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    return [];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                return data.cargoquery.map(&lt;br /&gt;
                    function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            item.title ||&lt;br /&gt;
                            item&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function makeInClause( ids ) {&lt;br /&gt;
&lt;br /&gt;
        return ids&lt;br /&gt;
            .map( String )&lt;br /&gt;
            .filter(&lt;br /&gt;
                function ( id ) {&lt;br /&gt;
                    return /^\d+$/.test(&lt;br /&gt;
                        id&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
            .join( &#039;,&#039; );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function uniqueIds( values ) {&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            ...new Set(&lt;br /&gt;
                values&lt;br /&gt;
                    .map( String )&lt;br /&gt;
                    .filter(&lt;br /&gt;
                        function ( id ) {&lt;br /&gt;
&lt;br /&gt;
                            return (&lt;br /&gt;
                                id &amp;amp;&amp;amp;&lt;br /&gt;
                                /^\d+$/.test(&lt;br /&gt;
                                    id&lt;br /&gt;
                                )&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
            )&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function mapBy(&lt;br /&gt;
        rows,&lt;br /&gt;
        key&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const result = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        rows.forEach(&lt;br /&gt;
            function ( row ) {&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    row[ key ] ===&lt;br /&gt;
                    undefined&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                result[&lt;br /&gt;
                    String(&lt;br /&gt;
                        row[ key ]&lt;br /&gt;
                    )&lt;br /&gt;
                ] = row;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return result;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 表示ヘルパー&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function textOrDash(&lt;br /&gt;
        value&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value === undefined ||&lt;br /&gt;
            value === null ||&lt;br /&gt;
            value === &#039;&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;―&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return String(&lt;br /&gt;
            value&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function cleanNumber(&lt;br /&gt;
    value&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        value === undefined ||&lt;br /&gt;
        value === null ||&lt;br /&gt;
        String( value ).trim() === &#039;&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const number =&lt;br /&gt;
        Number(&lt;br /&gt;
            value&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !Number.isFinite(&lt;br /&gt;
            number&lt;br /&gt;
        )&lt;br /&gt;
    ) {&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        Number.isInteger(&lt;br /&gt;
            number&lt;br /&gt;
        )&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        return String(&lt;br /&gt;
            number&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(&lt;br /&gt;
        Math.round(&lt;br /&gt;
            number * 100&lt;br /&gt;
        ) / 100&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 比較計算用数値&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function toFiniteNumber(&lt;br /&gt;
    value&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        value === undefined ||&lt;br /&gt;
        value === null ||&lt;br /&gt;
        String( value ).trim() === &#039;&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const number =&lt;br /&gt;
        Number(&lt;br /&gt;
            value&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !Number.isFinite(&lt;br /&gt;
            number&lt;br /&gt;
        )&lt;br /&gt;
    ) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return number;&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
    function formatHours(&lt;br /&gt;
        placement&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if ( !placement ) {&lt;br /&gt;
            return &#039;―&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const open =&lt;br /&gt;
            placement.opening_time || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        const close =&lt;br /&gt;
            placement.closing_time || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            open &amp;amp;&amp;amp;&lt;br /&gt;
            close&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            return (&lt;br /&gt;
                open +&lt;br /&gt;
                &#039;～&#039; +&lt;br /&gt;
                close&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( open ) {&lt;br /&gt;
&lt;br /&gt;
            return (&lt;br /&gt;
                open +&lt;br /&gt;
                &#039;～&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if ( close ) {&lt;br /&gt;
&lt;br /&gt;
            return (&lt;br /&gt;
                &#039;～&#039; +&lt;br /&gt;
                close&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            placement.hours_note&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            return placement.hours_note;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return &#039;未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function formatPositionStatus(&lt;br /&gt;
        status&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        switch ( status ) {&lt;br /&gt;
&lt;br /&gt;
case &#039;exact&#039;:&lt;br /&gt;
    return &#039;正確な位置&#039;;&lt;br /&gt;
&lt;br /&gt;
case &#039;approximate&#039;:&lt;br /&gt;
    return &#039;おおよその位置&#039;;&lt;br /&gt;
&lt;br /&gt;
default:&lt;br /&gt;
    return &#039;位置未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function formatVerification(&lt;br /&gt;
        status&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        switch ( status ) {&lt;br /&gt;
&lt;br /&gt;
            case &#039;verified&#039;:&lt;br /&gt;
                return &#039;確認済み&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;partially_verified&#039;:&lt;br /&gt;
                return &#039;一部確認済み&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;outdated&#039;:&lt;br /&gt;
                return &#039;情報が古い&#039;;&lt;br /&gt;
&lt;br /&gt;
            default:&lt;br /&gt;
                return &#039;未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function formatAvailability(&lt;br /&gt;
        status&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        switch ( status ) {&lt;br /&gt;
&lt;br /&gt;
            case &#039;available&#039;:&lt;br /&gt;
                return &#039;販売あり&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;unavailable&#039;:&lt;br /&gt;
                return &#039;販売なし&#039;;&lt;br /&gt;
&lt;br /&gt;
            default:&lt;br /&gt;
                return &#039;未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 単位価格&lt;br /&gt;
 * 表示用&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function getUnitPrice(&lt;br /&gt;
    offering&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const price =&lt;br /&gt;
        toFiniteNumber(&lt;br /&gt;
            offering.price&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    const quantity =&lt;br /&gt;
        toFiniteNumber(&lt;br /&gt;
            offering.serving_quantity&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        price === null ||&lt;br /&gt;
        quantity === null ||&lt;br /&gt;
        quantity &amp;lt;= 0&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        return &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const unitPrice =&lt;br /&gt;
        Math.round(&lt;br /&gt;
            (&lt;br /&gt;
                price /&lt;br /&gt;
                quantity&lt;br /&gt;
            ) *&lt;br /&gt;
            100&lt;br /&gt;
        ) /&lt;br /&gt;
        100;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const unit =&lt;br /&gt;
        offering.serving_unit ||&lt;br /&gt;
        &#039;単位&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return (&lt;br /&gt;
        unitPrice +&lt;br /&gt;
        &#039;円/&#039; +&lt;br /&gt;
        unit&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 単位価格&lt;br /&gt;
 * 比較計算用&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function getUnitPriceValue(&lt;br /&gt;
    offering&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const price =&lt;br /&gt;
        toFiniteNumber(&lt;br /&gt;
            offering.price&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    const quantity =&lt;br /&gt;
        toFiniteNumber(&lt;br /&gt;
            offering.serving_quantity&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        price === null ||&lt;br /&gt;
        quantity === null ||&lt;br /&gt;
        quantity &amp;lt;= 0&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        return null;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    return (&lt;br /&gt;
        price /&lt;br /&gt;
        quantity&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * DOM&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function createTextCell(&lt;br /&gt;
        tagName,&lt;br /&gt;
        text&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const cell =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                tagName&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        cell.textContent =&lt;br /&gt;
            text;&lt;br /&gt;
&lt;br /&gt;
        return cell;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * メニュー&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function createMenuList(&lt;br /&gt;
        menus&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const container =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        container.className =&lt;br /&gt;
            &#039;stall-compare-menu-list&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !menus ||&lt;br /&gt;
            menus.length === 0&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            container.textContent =&lt;br /&gt;
                &#039;メニュー未登録&#039;;&lt;br /&gt;
&lt;br /&gt;
            return container;&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        menus.forEach(&lt;br /&gt;
            function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                const menu =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                menu.className =&lt;br /&gt;
                    &#039;stall-compare-menu-item&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const name =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;strong&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                name.className =&lt;br /&gt;
                    &#039;stall-compare-menu-name&#039;;&lt;br /&gt;
&lt;br /&gt;
                name.textContent =&lt;br /&gt;
                    item.menuName ||&lt;br /&gt;
                    &#039;商品&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const price =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
price.className =&lt;br /&gt;
    &#039;stall-compare-menu-price&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
price.textContent =&lt;br /&gt;
    item.price&lt;br /&gt;
        ? item.price +&lt;br /&gt;
          &#039;円&#039;&lt;br /&gt;
        : &#039;価格未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 最安価格&lt;br /&gt;
 */&lt;br /&gt;
if (&lt;br /&gt;
    item.isLowestPrice&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const badge =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;span&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    badge.className =&lt;br /&gt;
        &#039;stall-compare-best-badge &#039; +&lt;br /&gt;
        &#039;stall-compare-best-price&#039;;&lt;br /&gt;
&lt;br /&gt;
    badge.textContent =&lt;br /&gt;
        &#039;最安価格&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    price.appendChild(&lt;br /&gt;
        document.createTextNode(&lt;br /&gt;
            &#039; &#039;&lt;br /&gt;
        )&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    price.appendChild(&lt;br /&gt;
        badge&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const serving =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    item.servingQuantity&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    serving.textContent =&lt;br /&gt;
                        &#039;内容量：&#039; +&lt;br /&gt;
                        item.servingQuantity +&lt;br /&gt;
                        (&lt;br /&gt;
                            item.servingUnit ||&lt;br /&gt;
                            &#039;&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                } else {&lt;br /&gt;
&lt;br /&gt;
                    serving.textContent =&lt;br /&gt;
                        &#039;内容量：未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const unit =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
unit.className =&lt;br /&gt;
    &#039;stall-compare-menu-unit-price&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
unit.textContent =&lt;br /&gt;
    &#039;1単位あたり：&#039; +&lt;br /&gt;
    item.unitPrice;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 最安単位価格&lt;br /&gt;
 */&lt;br /&gt;
if (&lt;br /&gt;
    item.isLowestUnitPrice&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const badge =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;span&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    badge.className =&lt;br /&gt;
        &#039;stall-compare-best-badge &#039; +&lt;br /&gt;
        &#039;stall-compare-best-unit-price&#039;;&lt;br /&gt;
&lt;br /&gt;
    badge.textContent =&lt;br /&gt;
        &#039;最安単位価格&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    unit.appendChild(&lt;br /&gt;
        document.createTextNode(&lt;br /&gt;
            &#039; &#039;&lt;br /&gt;
        )&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    unit.appendChild(&lt;br /&gt;
        badge&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const availability =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                availability.textContent =&lt;br /&gt;
                    &#039;販売状況：&#039; +&lt;br /&gt;
                    item.availability;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                menu.appendChild(&lt;br /&gt;
                    name&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                menu.appendChild(&lt;br /&gt;
                    price&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                menu.appendChild(&lt;br /&gt;
                    serving&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                menu.appendChild(&lt;br /&gt;
                    unit&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                menu.appendChild(&lt;br /&gt;
                    availability&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                container.appendChild(&lt;br /&gt;
                    menu&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return container;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * 比較表&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    function renderComparison(&lt;br /&gt;
        compareData&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        compareRoot.innerHTML =&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const heading =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;h2&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        heading.textContent =&lt;br /&gt;
            &#039;屋台比較&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        compareRoot.appendChild(&lt;br /&gt;
            heading&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const wrapper =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;div&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        wrapper.className =&lt;br /&gt;
            &#039;stall-compare-table-wrapper&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const table =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;table&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        table.className =&lt;br /&gt;
            &#039;stall-compare-table&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /* ------------------------------&lt;br /&gt;
         * thead&lt;br /&gt;
         * ------------------------------ */&lt;br /&gt;
&lt;br /&gt;
        const thead =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;thead&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const headerRow =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;tr&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        headerRow.appendChild(&lt;br /&gt;
            createTextCell(&lt;br /&gt;
                &#039;th&#039;,&lt;br /&gt;
                &#039;比較項目&#039;&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        compareData.forEach(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                const th =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;th&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data.stall &amp;amp;&amp;amp;&lt;br /&gt;
                    data.stall.page_name&lt;br /&gt;
                ) {&lt;br /&gt;
&lt;br /&gt;
                    const link =&lt;br /&gt;
                        document.createElement(&lt;br /&gt;
                            &#039;a&#039;&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    link.href =&lt;br /&gt;
                        mw.util.getUrl(&lt;br /&gt;
                            data.stall&lt;br /&gt;
                                .page_name&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    link.textContent =&lt;br /&gt;
                        data.stall&lt;br /&gt;
                            .stall_name ||&lt;br /&gt;
                        &#039;屋台&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    th.appendChild(&lt;br /&gt;
                        link&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                } else {&lt;br /&gt;
&lt;br /&gt;
                    th.textContent =&lt;br /&gt;
                        data.stall&lt;br /&gt;
                            ? data.stall.stall_name&lt;br /&gt;
                            : &#039;屋台&#039;;&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                headerRow.appendChild(&lt;br /&gt;
                    th&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        thead.appendChild(&lt;br /&gt;
            headerRow&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        table.appendChild(&lt;br /&gt;
            thead&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const tbody =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;tbody&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        function addRow(&lt;br /&gt;
            label,&lt;br /&gt;
            getter&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            const tr =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;tr&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const labelCell =&lt;br /&gt;
                createTextCell(&lt;br /&gt;
                    &#039;th&#039;,&lt;br /&gt;
                    label&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            labelCell.scope =&lt;br /&gt;
                &#039;row&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            tr.appendChild(&lt;br /&gt;
                labelCell&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            compareData.forEach(&lt;br /&gt;
                function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                    tr.appendChild(&lt;br /&gt;
                        createTextCell(&lt;br /&gt;
                            &#039;td&#039;,&lt;br /&gt;
                            textOrDash(&lt;br /&gt;
                                getter(&lt;br /&gt;
                                    data&lt;br /&gt;
                                )&lt;br /&gt;
                            )&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            tbody.appendChild(&lt;br /&gt;
                tr&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /* =================================&lt;br /&gt;
         * Placement情報&lt;br /&gt;
         * ================================= */&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;開催年&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.placement&lt;br /&gt;
                    ? data.placement.year +&lt;br /&gt;
                      &#039;年&#039;&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;祭り&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.festival&lt;br /&gt;
                    ? data.festival&lt;br /&gt;
                        .festival_name&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;会場&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.venue&lt;br /&gt;
                    ? data.venue&lt;br /&gt;
                        .venue_name&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;地域&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.area&lt;br /&gt;
                    ? data.area&lt;br /&gt;
                        .area_name&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;カテゴリ&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.stall&lt;br /&gt;
                    ? data.stall&lt;br /&gt;
                        .category&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;出店場所&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.placement&lt;br /&gt;
                    ? data.placement&lt;br /&gt;
                        .location_note&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;営業時間&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return formatHours(&lt;br /&gt;
                    data.placement&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;位置情報&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.placement&lt;br /&gt;
                    ? formatPositionStatus(&lt;br /&gt;
                        data.placement&lt;br /&gt;
                            .position_status&lt;br /&gt;
                    )&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        addRow(&lt;br /&gt;
            &#039;確認状態&#039;,&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                return data.placement&lt;br /&gt;
                    ? formatVerification(&lt;br /&gt;
                        data.placement&lt;br /&gt;
                            .verification_status&lt;br /&gt;
                    )&lt;br /&gt;
                    : &#039;―&#039;;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /* =================================&lt;br /&gt;
         * メニュー&lt;br /&gt;
         * ================================= */&lt;br /&gt;
&lt;br /&gt;
        const menuRow =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;tr&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const menuLabel =&lt;br /&gt;
            createTextCell(&lt;br /&gt;
                &#039;th&#039;,&lt;br /&gt;
                &#039;メニュー&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        menuLabel.scope =&lt;br /&gt;
            &#039;row&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        menuRow.appendChild(&lt;br /&gt;
            menuLabel&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        compareData.forEach(&lt;br /&gt;
            function ( data ) {&lt;br /&gt;
&lt;br /&gt;
                const td =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;td&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                td.appendChild(&lt;br /&gt;
                    createMenuList(&lt;br /&gt;
                        data.menus&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                menuRow.appendChild(&lt;br /&gt;
                    td&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        tbody.appendChild(&lt;br /&gt;
            menuRow&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        table.appendChild(&lt;br /&gt;
            tbody&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        wrapper.appendChild(&lt;br /&gt;
            table&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        compareRoot.appendChild(&lt;br /&gt;
            wrapper&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const note =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;p&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        note.className =&lt;br /&gt;
            &#039;stall-compare-note&#039;;&lt;br /&gt;
&lt;br /&gt;
        note.textContent =&lt;br /&gt;
            &#039;この比較は出店単位（Placement）です。祭り・開催年・会場ごとの価格、営業時間、出店位置を比較しています。&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        compareRoot.appendChild(&lt;br /&gt;
            note&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function renderMessage(&lt;br /&gt;
        message&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        compareRoot.innerHTML =&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        const p =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                &#039;p&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        p.className =&lt;br /&gt;
            &#039;stall-compare-page-message&#039;;&lt;br /&gt;
&lt;br /&gt;
        p.textContent =&lt;br /&gt;
            message;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        compareRoot.appendChild(&lt;br /&gt;
            p&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =====================================&lt;br /&gt;
     * データ取得&lt;br /&gt;
     * ===================================== */&lt;br /&gt;
&lt;br /&gt;
    const placementIds =&lt;br /&gt;
        getPlacementIds();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        placementIds.length &amp;lt;&lt;br /&gt;
        MIN_COMPARE&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        renderMessage(&lt;br /&gt;
            &#039;比較する出店を2件以上選択してください。&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * STEP 1&lt;br /&gt;
     * Placementを直接取得&lt;br /&gt;
     */&lt;br /&gt;
    cargoQuery(&lt;br /&gt;
&lt;br /&gt;
        &#039;FestivalStallPlacements&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;placement_id=placement_id,&#039; +&lt;br /&gt;
        &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
        &#039;festival_id=festival_id,&#039; +&lt;br /&gt;
        &#039;venue_id=venue_id,&#039; +&lt;br /&gt;
        &#039;year=year,&#039; +&lt;br /&gt;
        &#039;latitude=latitude,&#039; +&lt;br /&gt;
        &#039;longitude=longitude,&#039; +&lt;br /&gt;
        &#039;location_note=location_note,&#039; +&lt;br /&gt;
        &#039;opening_time=opening_time,&#039; +&lt;br /&gt;
        &#039;closing_time=closing_time,&#039; +&lt;br /&gt;
        &#039;hours_note=hours_note,&#039; +&lt;br /&gt;
        &#039;position_status=position_status,&#039; +&lt;br /&gt;
        &#039;verification_status=verification_status&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;placement_id IN (&#039; +&lt;br /&gt;
        makeInClause(&lt;br /&gt;
            placementIds&lt;br /&gt;
        ) +&lt;br /&gt;
        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
        100&lt;br /&gt;
&lt;br /&gt;
    ).then(&lt;br /&gt;
        function ( placements ) {&lt;br /&gt;
&lt;br /&gt;
            const stallIds =&lt;br /&gt;
                uniqueIds(&lt;br /&gt;
                    placements.map(&lt;br /&gt;
                        function ( row ) {&lt;br /&gt;
                            return row.stall_id;&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const festivalIds =&lt;br /&gt;
                uniqueIds(&lt;br /&gt;
                    placements.map(&lt;br /&gt;
                        function ( row ) {&lt;br /&gt;
                            return row.festival_id;&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const venueIds =&lt;br /&gt;
                uniqueIds(&lt;br /&gt;
                    placements.map(&lt;br /&gt;
                        function ( row ) {&lt;br /&gt;
                            return row.venue_id;&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * STEP 2&lt;br /&gt;
             */&lt;br /&gt;
            return Promise.all( [&lt;br /&gt;
&lt;br /&gt;
                stallIds.length&lt;br /&gt;
                    ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                        &#039;Stalls&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
                        &#039;name=stall_name,&#039; +&lt;br /&gt;
                        &#039;category=category,&#039; +&lt;br /&gt;
                        &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;stall_id IN (&#039; +&lt;br /&gt;
                        makeInClause(&lt;br /&gt;
                            stallIds&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                        100&lt;br /&gt;
&lt;br /&gt;
                    )&lt;br /&gt;
                    : Promise.resolve(&lt;br /&gt;
                        []&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                festivalIds.length&lt;br /&gt;
                    ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                        &#039;Festivals&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;festival_id=festival_id,&#039; +&lt;br /&gt;
                        &#039;name=festival_name,&#039; +&lt;br /&gt;
                        &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;festival_id IN (&#039; +&lt;br /&gt;
                        makeInClause(&lt;br /&gt;
                            festivalIds&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                        100&lt;br /&gt;
&lt;br /&gt;
                    )&lt;br /&gt;
                    : Promise.resolve(&lt;br /&gt;
                        []&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                venueIds.length&lt;br /&gt;
                    ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                        &#039;Venues&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;venue_id=venue_id,&#039; +&lt;br /&gt;
                        &#039;name=venue_name,&#039; +&lt;br /&gt;
                        &#039;area_id=area_id,&#039; +&lt;br /&gt;
                        &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;venue_id IN (&#039; +&lt;br /&gt;
                        makeInClause(&lt;br /&gt;
                            venueIds&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                        100&lt;br /&gt;
&lt;br /&gt;
                    )&lt;br /&gt;
                    : Promise.resolve(&lt;br /&gt;
                        []&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                    &#039;FestivalStallMenuOfferings&#039;,&lt;br /&gt;
&lt;br /&gt;
                    &#039;placement_id=placement_id,&#039; +&lt;br /&gt;
                    &#039;menu_item_id=menu_item_id,&#039; +&lt;br /&gt;
                    &#039;price=price,&#039; +&lt;br /&gt;
                    &#039;serving_quantity=serving_quantity,&#039; +&lt;br /&gt;
                    &#039;serving_unit=serving_unit,&#039; +&lt;br /&gt;
                    &#039;availability=availability,&#039; +&lt;br /&gt;
                    &#039;verification_status=verification_status,&#039; +&lt;br /&gt;
                    &#039;sort_order=sort_order&#039;,&lt;br /&gt;
&lt;br /&gt;
                    &#039;placement_id IN (&#039; +&lt;br /&gt;
                    makeInClause(&lt;br /&gt;
                        placementIds&lt;br /&gt;
                    ) +&lt;br /&gt;
                    &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                    100&lt;br /&gt;
&lt;br /&gt;
                )&lt;br /&gt;
&lt;br /&gt;
            ] ).then(&lt;br /&gt;
                function ( results ) {&lt;br /&gt;
&lt;br /&gt;
                    return {&lt;br /&gt;
&lt;br /&gt;
                        placements:&lt;br /&gt;
                            placements,&lt;br /&gt;
&lt;br /&gt;
                        stalls:&lt;br /&gt;
                            results[ 0 ],&lt;br /&gt;
&lt;br /&gt;
                        festivals:&lt;br /&gt;
                            results[ 1 ],&lt;br /&gt;
&lt;br /&gt;
                        venues:&lt;br /&gt;
                            results[ 2 ],&lt;br /&gt;
&lt;br /&gt;
                        offerings:&lt;br /&gt;
                            results[ 3 ]&lt;br /&gt;
&lt;br /&gt;
                    };&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    ).then(&lt;br /&gt;
        function ( data ) {&lt;br /&gt;
&lt;br /&gt;
            const areaIds =&lt;br /&gt;
                uniqueIds(&lt;br /&gt;
                    data.venues.map(&lt;br /&gt;
                        function ( row ) {&lt;br /&gt;
                            return row.area_id;&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const menuItemIds =&lt;br /&gt;
                uniqueIds(&lt;br /&gt;
                    data.offerings.map(&lt;br /&gt;
                        function ( row ) {&lt;br /&gt;
                            return row.menu_item_id;&lt;br /&gt;
                        }&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * STEP 3&lt;br /&gt;
             */&lt;br /&gt;
            return Promise.all( [&lt;br /&gt;
&lt;br /&gt;
                areaIds.length&lt;br /&gt;
                    ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                        &#039;Areas&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;area_id=area_id,&#039; +&lt;br /&gt;
                        &#039;name=area_name,&#039; +&lt;br /&gt;
                        &#039;_pageName=page_name&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;area_id IN (&#039; +&lt;br /&gt;
                        makeInClause(&lt;br /&gt;
                            areaIds&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                        100&lt;br /&gt;
&lt;br /&gt;
                    )&lt;br /&gt;
                    : Promise.resolve(&lt;br /&gt;
                        []&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                menuItemIds.length&lt;br /&gt;
                    ? cargoQuery(&lt;br /&gt;
&lt;br /&gt;
                        &#039;StallMenuItems&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;menu_item_id=menu_item_id,&#039; +&lt;br /&gt;
&#039;stall_id=stall_id,&#039; +&lt;br /&gt;
&#039;name=menu_name,&#039; +&lt;br /&gt;
&#039;item_category=item_category&#039;,&lt;br /&gt;
&lt;br /&gt;
                        &#039;menu_item_id IN (&#039; +&lt;br /&gt;
                        makeInClause(&lt;br /&gt;
                            menuItemIds&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;)&#039;,&lt;br /&gt;
&lt;br /&gt;
                        100&lt;br /&gt;
&lt;br /&gt;
                    )&lt;br /&gt;
                    : Promise.resolve(&lt;br /&gt;
                        []&lt;br /&gt;
                    )&lt;br /&gt;
&lt;br /&gt;
            ] ).then(&lt;br /&gt;
                function ( results ) {&lt;br /&gt;
&lt;br /&gt;
                    data.areas =&lt;br /&gt;
                        results[ 0 ];&lt;br /&gt;
&lt;br /&gt;
                    data.menuItems =&lt;br /&gt;
                        results[ 1 ];&lt;br /&gt;
&lt;br /&gt;
                    return data;&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    ).then(&lt;br /&gt;
        function ( data ) {&lt;br /&gt;
&lt;br /&gt;
            const placementMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.placements,&lt;br /&gt;
                    &#039;placement_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const stallMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.stalls,&lt;br /&gt;
                    &#039;stall_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const festivalMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.festivals,&lt;br /&gt;
                    &#039;festival_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const venueMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.venues,&lt;br /&gt;
                    &#039;venue_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const areaMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.areas,&lt;br /&gt;
                    &#039;area_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const menuMap =&lt;br /&gt;
                mapBy(&lt;br /&gt;
                    data.menuItems,&lt;br /&gt;
                    &#039;menu_item_id&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Offering&lt;br /&gt;
             * placement単位&lt;br /&gt;
             */&lt;br /&gt;
            const offeringsByPlacement =&lt;br /&gt;
                {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            data.offerings&lt;br /&gt;
                .slice()&lt;br /&gt;
                .sort(&lt;br /&gt;
                    function ( a, b ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            Number(&lt;br /&gt;
                                a.sort_order || 0&lt;br /&gt;
                            ) -&lt;br /&gt;
                            Number(&lt;br /&gt;
                                b.sort_order || 0&lt;br /&gt;
                            )&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                )&lt;br /&gt;
                .forEach(&lt;br /&gt;
                    function ( offering ) {&lt;br /&gt;
&lt;br /&gt;
                        const placementId =&lt;br /&gt;
                            String(&lt;br /&gt;
                                offering&lt;br /&gt;
                                    .placement_id&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            !offeringsByPlacement[&lt;br /&gt;
                                placementId&lt;br /&gt;
                            ]&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            offeringsByPlacement[&lt;br /&gt;
                                placementId&lt;br /&gt;
                            ] = [];&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        offeringsByPlacement[&lt;br /&gt;
                            placementId&lt;br /&gt;
                        ].push(&lt;br /&gt;
                            offering&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * localStorage順を維持&lt;br /&gt;
             */&lt;br /&gt;
            const compareData =&lt;br /&gt;
                placementIds.map(&lt;br /&gt;
                    function (&lt;br /&gt;
                        placementId&lt;br /&gt;
                    ) {&lt;br /&gt;
&lt;br /&gt;
                        const placement =&lt;br /&gt;
                            placementMap[&lt;br /&gt;
                                placementId&lt;br /&gt;
                            ] || null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if ( !placement ) {&lt;br /&gt;
&lt;br /&gt;
                            return {&lt;br /&gt;
&lt;br /&gt;
                                placementId:&lt;br /&gt;
                                    placementId,&lt;br /&gt;
&lt;br /&gt;
                                placement:&lt;br /&gt;
                                    null,&lt;br /&gt;
&lt;br /&gt;
                                stall:&lt;br /&gt;
                                    null,&lt;br /&gt;
&lt;br /&gt;
                                festival:&lt;br /&gt;
                                    null,&lt;br /&gt;
&lt;br /&gt;
                                venue:&lt;br /&gt;
                                    null,&lt;br /&gt;
&lt;br /&gt;
                                area:&lt;br /&gt;
                                    null,&lt;br /&gt;
&lt;br /&gt;
                                menus:&lt;br /&gt;
                                    []&lt;br /&gt;
&lt;br /&gt;
                            };&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const stall =&lt;br /&gt;
                            stallMap[&lt;br /&gt;
                                String(&lt;br /&gt;
                                    placement.stall_id&lt;br /&gt;
                                )&lt;br /&gt;
                            ] || null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const festival =&lt;br /&gt;
                            festivalMap[&lt;br /&gt;
                                String(&lt;br /&gt;
                                    placement.festival_id&lt;br /&gt;
                                )&lt;br /&gt;
                            ] || null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const venue =&lt;br /&gt;
                            venueMap[&lt;br /&gt;
                                String(&lt;br /&gt;
                                    placement.venue_id&lt;br /&gt;
                                )&lt;br /&gt;
                            ] || null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        let area = null;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            venue &amp;amp;&amp;amp;&lt;br /&gt;
                            venue.area_id&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            area =&lt;br /&gt;
                                areaMap[&lt;br /&gt;
                                    String(&lt;br /&gt;
                                        venue.area_id&lt;br /&gt;
                                    )&lt;br /&gt;
                                ] || null;&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                        const offerings =&lt;br /&gt;
                            offeringsByPlacement[&lt;br /&gt;
                                placementId&lt;br /&gt;
                            ] || [];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * Placementに紐づくメニューを生成&lt;br /&gt;
 */&lt;br /&gt;
const menus =&lt;br /&gt;
    offerings.map(&lt;br /&gt;
        function ( offering ) {&lt;br /&gt;
&lt;br /&gt;
            const menu =&lt;br /&gt;
                menuMap[&lt;br /&gt;
                    String(&lt;br /&gt;
                        offering.menu_item_id&lt;br /&gt;
                    )&lt;br /&gt;
                ] || {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * どの出店の商品か&lt;br /&gt;
                 */&lt;br /&gt;
                placementId:&lt;br /&gt;
                    placementId,&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                menuName:&lt;br /&gt;
                    menu.menu_name ||&lt;br /&gt;
                    &#039;商品&#039;,&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                category:&lt;br /&gt;
                    menu.item_category ||&lt;br /&gt;
                    &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 表示価格&lt;br /&gt;
                 */&lt;br /&gt;
                price:&lt;br /&gt;
                    cleanNumber(&lt;br /&gt;
                        offering.price&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 比較用価格&lt;br /&gt;
                 */&lt;br /&gt;
                priceValue:&lt;br /&gt;
                    toFiniteNumber(&lt;br /&gt;
                        offering.price&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                servingQuantity:&lt;br /&gt;
                    cleanNumber(&lt;br /&gt;
                        offering&lt;br /&gt;
                            .serving_quantity&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                servingUnit:&lt;br /&gt;
                    offering&lt;br /&gt;
                        .serving_unit ||&lt;br /&gt;
                    &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 表示用単位価格&lt;br /&gt;
                 */&lt;br /&gt;
                unitPrice:&lt;br /&gt;
                    getUnitPrice(&lt;br /&gt;
                        offering&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 比較用単位価格&lt;br /&gt;
                 */&lt;br /&gt;
                unitPriceValue:&lt;br /&gt;
                    getUnitPriceValue(&lt;br /&gt;
                        offering&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                availability:&lt;br /&gt;
                    formatAvailability(&lt;br /&gt;
                        offering&lt;br /&gt;
                            .availability&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                verification:&lt;br /&gt;
                    formatVerification(&lt;br /&gt;
                        offering&lt;br /&gt;
                            .verification_status&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                isLowestPrice:&lt;br /&gt;
                    false,&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                isLowestUnitPrice:&lt;br /&gt;
                    false&lt;br /&gt;
&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
return {&lt;br /&gt;
&lt;br /&gt;
    placementId:&lt;br /&gt;
        placementId,&lt;br /&gt;
&lt;br /&gt;
    placement:&lt;br /&gt;
        placement,&lt;br /&gt;
&lt;br /&gt;
    stall:&lt;br /&gt;
        stall,&lt;br /&gt;
&lt;br /&gt;
    festival:&lt;br /&gt;
        festival,&lt;br /&gt;
&lt;br /&gt;
    venue:&lt;br /&gt;
        venue,&lt;br /&gt;
&lt;br /&gt;
    area:&lt;br /&gt;
        area,&lt;br /&gt;
&lt;br /&gt;
    menus:&lt;br /&gt;
        menus&lt;br /&gt;
&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
);&lt;br /&gt;
                        &lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 最安価格・最安単位価格&lt;br /&gt;
 *&lt;br /&gt;
 * 「同じ商品名 ＋ 同じ単位」&lt;br /&gt;
 * の商品だけを比較する&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function markBestPrices(&lt;br /&gt;
    compareData&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const allMenus = [];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 比較文字列を正規化&lt;br /&gt;
     *&lt;br /&gt;
     * 例:&lt;br /&gt;
     * &amp;quot;たこ焼き&amp;quot;&lt;br /&gt;
     * &amp;quot; たこ焼き &amp;quot;&lt;br /&gt;
     *&lt;br /&gt;
     * を同じものとして扱う&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    function normalizeCompareText(&lt;br /&gt;
        value&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value === undefined ||&lt;br /&gt;
            value === null&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let text =&lt;br /&gt;
            String(&lt;br /&gt;
                value&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 全角・半角などを可能な範囲で統一&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            typeof text.normalize ===&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            text =&lt;br /&gt;
                text.normalize(&lt;br /&gt;
                    &#039;NFKC&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 連続空白を1つにする&lt;br /&gt;
         */&lt;br /&gt;
        text =&lt;br /&gt;
            text.replace(&lt;br /&gt;
                /\s+/g,&lt;br /&gt;
                &#039; &#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 英字商品名にも対応&lt;br /&gt;
         */&lt;br /&gt;
        text =&lt;br /&gt;
            text.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return text;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 全メニューを集める&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    compareData.forEach(&lt;br /&gt;
        function ( data ) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !data.menus ||&lt;br /&gt;
                !Array.isArray(&lt;br /&gt;
                    data.menus&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            data.menus.forEach(&lt;br /&gt;
                function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * 毎回初期化&lt;br /&gt;
                     */&lt;br /&gt;
                    menu.isLowestPrice =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    menu.isLowestUnitPrice =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * 比較用の商品名&lt;br /&gt;
                     */&lt;br /&gt;
                    menu.compareMenuName =&lt;br /&gt;
                        normalizeCompareText(&lt;br /&gt;
                            menu.menuName&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * 比較用単位&lt;br /&gt;
                     */&lt;br /&gt;
                    menu.compareUnit =&lt;br /&gt;
                        normalizeCompareText(&lt;br /&gt;
                            menu.servingUnit&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    allMenus.push(&lt;br /&gt;
                        menu&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 商品名＋単位ごとのグループ&lt;br /&gt;
     *&lt;br /&gt;
     * 例:&lt;br /&gt;
     *&lt;br /&gt;
     * たこ焼き + 個&lt;br /&gt;
     * 焼きそば + パック&lt;br /&gt;
     * りんご飴 + 本&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    const groups = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    allMenus.forEach(&lt;br /&gt;
        function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 商品名が無ければ比較しない&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                !menu.compareMenuName&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 単位が無ければ比較しない&lt;br /&gt;
             *&lt;br /&gt;
             * 「同じ商品名＋同じ単位」&lt;br /&gt;
             * が条件だから&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                !menu.compareUnit&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const groupKey =&lt;br /&gt;
                menu.compareMenuName +&lt;br /&gt;
                &#039;||&#039; +&lt;br /&gt;
                menu.compareUnit;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !groups[&lt;br /&gt;
                    groupKey&lt;br /&gt;
                ]&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                groups[&lt;br /&gt;
                    groupKey&lt;br /&gt;
                ] = [];&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            groups[&lt;br /&gt;
                groupKey&lt;br /&gt;
            ].push(&lt;br /&gt;
                menu&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * グループごとに判定&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    Object.keys(&lt;br /&gt;
        groups&lt;br /&gt;
    ).forEach(&lt;br /&gt;
        function ( groupKey ) {&lt;br /&gt;
&lt;br /&gt;
            const menus =&lt;br /&gt;
                groups[&lt;br /&gt;
                    groupKey&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 2出店以上あるか確認&lt;br /&gt;
             *&lt;br /&gt;
             * 同じ出店内だけの商品比較は&lt;br /&gt;
             * 「最安」としない&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const placementIds =&lt;br /&gt;
                [&lt;br /&gt;
                    ...new Set(&lt;br /&gt;
                        menus.map(&lt;br /&gt;
                            function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                                return String(&lt;br /&gt;
                                    menu.placementId&lt;br /&gt;
                                );&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                placementIds.length &amp;lt; 2&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 最安価格&lt;br /&gt;
             *&lt;br /&gt;
             * 同商品＋同単位の&lt;br /&gt;
             * 販売価格を比較&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const priceCandidates =&lt;br /&gt;
                menus.filter(&lt;br /&gt;
                    function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            menu.priceValue !==&lt;br /&gt;
                                null &amp;amp;&amp;amp;&lt;br /&gt;
                            Number.isFinite(&lt;br /&gt;
                                menu.priceValue&lt;br /&gt;
                            )&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 価格が登録されている&lt;br /&gt;
             * 出店が2件以上あるか&lt;br /&gt;
             */&lt;br /&gt;
            const pricePlacementIds =&lt;br /&gt;
                [&lt;br /&gt;
                    ...new Set(&lt;br /&gt;
                        priceCandidates.map(&lt;br /&gt;
                            function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                                return String(&lt;br /&gt;
                                    menu.placementId&lt;br /&gt;
                                );&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                pricePlacementIds.length &amp;gt;= 2&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const lowestPrice =&lt;br /&gt;
                    Math.min.apply(&lt;br /&gt;
                        null,&lt;br /&gt;
                        priceCandidates.map(&lt;br /&gt;
                            function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                                return menu&lt;br /&gt;
                                    .priceValue;&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                priceCandidates.forEach(&lt;br /&gt;
                    function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                        /*&lt;br /&gt;
                         * 円なので通常整数だが&lt;br /&gt;
                         * 小数にも一応対応&lt;br /&gt;
                         */&lt;br /&gt;
                        if (&lt;br /&gt;
                            Math.abs(&lt;br /&gt;
                                menu.priceValue -&lt;br /&gt;
                                lowestPrice&lt;br /&gt;
                            ) &amp;lt;&lt;br /&gt;
                            0.000001&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            menu.isLowestPrice =&lt;br /&gt;
                                true;&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 最安単位価格&lt;br /&gt;
             *&lt;br /&gt;
             * 同商品＋同単位で&lt;br /&gt;
             * price / quantity を比較&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const unitPriceCandidates =&lt;br /&gt;
                menus.filter(&lt;br /&gt;
                    function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            menu.unitPriceValue !==&lt;br /&gt;
                                null &amp;amp;&amp;amp;&lt;br /&gt;
                            Number.isFinite(&lt;br /&gt;
                                menu.unitPriceValue&lt;br /&gt;
                            )&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const unitPricePlacementIds =&lt;br /&gt;
                [&lt;br /&gt;
                    ...new Set(&lt;br /&gt;
                        unitPriceCandidates.map(&lt;br /&gt;
                            function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                                return String(&lt;br /&gt;
                                    menu.placementId&lt;br /&gt;
                                );&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                unitPricePlacementIds.length &amp;gt;= 2&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const lowestUnitPrice =&lt;br /&gt;
                    Math.min.apply(&lt;br /&gt;
                        null,&lt;br /&gt;
                        unitPriceCandidates.map(&lt;br /&gt;
                            function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                                return menu&lt;br /&gt;
                                    .unitPriceValue;&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                unitPriceCandidates.forEach(&lt;br /&gt;
                    function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                        /*&lt;br /&gt;
                         * 割り算による&lt;br /&gt;
                         * 浮動小数誤差対策&lt;br /&gt;
                         */&lt;br /&gt;
                        if (&lt;br /&gt;
                            Math.abs(&lt;br /&gt;
                                menu.unitPriceValue -&lt;br /&gt;
                                lowestUnitPrice&lt;br /&gt;
                            ) &amp;lt;&lt;br /&gt;
                            0.000001&lt;br /&gt;
                        ) {&lt;br /&gt;
&lt;br /&gt;
                            menu.isLowestUnitPrice =&lt;br /&gt;
                                true;&lt;br /&gt;
&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * 商品別比較サマリー&lt;br /&gt;
 *&lt;br /&gt;
 * 同じ商品名 + 同じ単位でグループ化&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
&lt;br /&gt;
function renderProductGroupSummary(&lt;br /&gt;
    compareData&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const comparePage =&lt;br /&gt;
        document.getElementById(&lt;br /&gt;
            &#039;stall-compare-page&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !comparePage&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 文字列正規化&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    function normalizeText(&lt;br /&gt;
        value&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value === undefined ||&lt;br /&gt;
            value === null&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let text =&lt;br /&gt;
            String(&lt;br /&gt;
                value&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            typeof text.normalize ===&lt;br /&gt;
            &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            text =&lt;br /&gt;
                text.normalize(&lt;br /&gt;
                    &#039;NFKC&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        text =&lt;br /&gt;
            text.replace(&lt;br /&gt;
                /\s+/g,&lt;br /&gt;
                &#039; &#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        return text;&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
/* =================================&lt;br /&gt;
 * 屋台ページリンクを生成&lt;br /&gt;
 * ================================= */&lt;br /&gt;
&lt;br /&gt;
function appendStallLinks(&lt;br /&gt;
    container,&lt;br /&gt;
    items&lt;br /&gt;
) {&lt;br /&gt;
&lt;br /&gt;
    const stalls = [];&lt;br /&gt;
    const seen = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    items.forEach(&lt;br /&gt;
        function ( item ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * page_nameがある場合は&lt;br /&gt;
             * page_nameで重複判定&lt;br /&gt;
             *&lt;br /&gt;
             * 無い場合は名前で判定&lt;br /&gt;
             */&lt;br /&gt;
            const key =&lt;br /&gt;
                item.stallPage&lt;br /&gt;
                    ? &#039;page:&#039; +&lt;br /&gt;
                      item.stallPage&lt;br /&gt;
                    : &#039;name:&#039; +&lt;br /&gt;
                      item.stallName;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                seen[&lt;br /&gt;
                    key&lt;br /&gt;
                ]&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            seen[&lt;br /&gt;
                key&lt;br /&gt;
            ] = true;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            stalls.push(&lt;br /&gt;
                {&lt;br /&gt;
                    name:&lt;br /&gt;
                        item.stallName,&lt;br /&gt;
&lt;br /&gt;
                    page:&lt;br /&gt;
                        item.stallPage&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    stalls.forEach(&lt;br /&gt;
        function (&lt;br /&gt;
            stall,&lt;br /&gt;
            index&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 2件目以降の区切り&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                index &amp;gt; 0&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                container.appendChild(&lt;br /&gt;
                    document.createTextNode(&lt;br /&gt;
                        &#039;・&#039;&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * ページが存在する場合&lt;br /&gt;
             * リンクにする&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                stall.page&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const link =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;a&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                link.href =&lt;br /&gt;
                    mw.util.getUrl(&lt;br /&gt;
                        stall.page&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                link.textContent =&lt;br /&gt;
                    stall.name;&lt;br /&gt;
&lt;br /&gt;
                link.className =&lt;br /&gt;
                    &#039;stall-product-group-stall-link&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                container.appendChild(&lt;br /&gt;
                    link&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            } else {&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * page_nameが取得できない場合&lt;br /&gt;
                 * 普通の文字として表示&lt;br /&gt;
                 */&lt;br /&gt;
                container.appendChild(&lt;br /&gt;
                    document.createTextNode(&lt;br /&gt;
                        stall.name&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 商品グループ作成&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    const groups = {};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    compareData.forEach(&lt;br /&gt;
        function ( data ) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !data.menus ||&lt;br /&gt;
                !Array.isArray(&lt;br /&gt;
                    data.menus&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            data.menus.forEach(&lt;br /&gt;
                function ( menu ) {&lt;br /&gt;
&lt;br /&gt;
                    const menuName =&lt;br /&gt;
                        normalizeText(&lt;br /&gt;
                            menu.menuName&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    const unit =&lt;br /&gt;
                        normalizeText(&lt;br /&gt;
                            menu.servingUnit&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * 商品名または単位が無いものは&lt;br /&gt;
                     * 商品比較サマリーから除外&lt;br /&gt;
                     */&lt;br /&gt;
                    if (&lt;br /&gt;
                        !menuName ||&lt;br /&gt;
                        !unit&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    const key =&lt;br /&gt;
                        menuName.toLowerCase() +&lt;br /&gt;
                        &#039;||&#039; +&lt;br /&gt;
                        unit.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        !groups[&lt;br /&gt;
                            key&lt;br /&gt;
                        ]&lt;br /&gt;
                    ) {&lt;br /&gt;
&lt;br /&gt;
                        groups[&lt;br /&gt;
                            key&lt;br /&gt;
                        ] = {&lt;br /&gt;
&lt;br /&gt;
                            menuName:&lt;br /&gt;
                                menuName,&lt;br /&gt;
&lt;br /&gt;
                            unit:&lt;br /&gt;
                                unit,&lt;br /&gt;
&lt;br /&gt;
                            items:&lt;br /&gt;
                                []&lt;br /&gt;
&lt;br /&gt;
                        };&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                    groups[&lt;br /&gt;
    key&lt;br /&gt;
].items.push(&lt;br /&gt;
    {&lt;br /&gt;
&lt;br /&gt;
        placementId:&lt;br /&gt;
            String(&lt;br /&gt;
                menu.placementId&lt;br /&gt;
            ),&lt;br /&gt;
&lt;br /&gt;
        stallName:&lt;br /&gt;
            (&lt;br /&gt;
                data.stall &amp;amp;&amp;amp;&lt;br /&gt;
                data.stall.stall_name&lt;br /&gt;
            )&lt;br /&gt;
                ? data.stall.stall_name&lt;br /&gt;
                : &#039;屋台&#039;,&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 屋台ページ名&lt;br /&gt;
         */&lt;br /&gt;
        stallPage:&lt;br /&gt;
            (&lt;br /&gt;
                data.stall &amp;amp;&amp;amp;&lt;br /&gt;
                data.stall.page_name&lt;br /&gt;
            )&lt;br /&gt;
                ? data.stall.page_name&lt;br /&gt;
                : &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
        menu:&lt;br /&gt;
            menu&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const groupKeys =&lt;br /&gt;
        Object.keys(&lt;br /&gt;
            groups&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        groupKeys.length === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * サマリー全体&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    const summary =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;section&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    summary.className =&lt;br /&gt;
        &#039;stall-product-group-summary&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    const title =&lt;br /&gt;
        document.createElement(&lt;br /&gt;
            &#039;h2&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    title.className =&lt;br /&gt;
        &#039;stall-product-group-summary-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent =&lt;br /&gt;
        &#039;商品別比較サマリー&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    summary.appendChild(&lt;br /&gt;
        title&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =================================&lt;br /&gt;
     * 各商品グループ&lt;br /&gt;
     * ================================= */&lt;br /&gt;
&lt;br /&gt;
    groupKeys.forEach(&lt;br /&gt;
        function ( key ) {&lt;br /&gt;
&lt;br /&gt;
            const group =&lt;br /&gt;
                groups[&lt;br /&gt;
                    key&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
            const items =&lt;br /&gt;
                group.items;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 同じPlacementを重複カウントしない&lt;br /&gt;
             */&lt;br /&gt;
            const placementIds =&lt;br /&gt;
                [&lt;br /&gt;
                    ...new Set(&lt;br /&gt;
                        items.map(&lt;br /&gt;
                            function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                                return item&lt;br /&gt;
                                    .placementId;&lt;br /&gt;
&lt;br /&gt;
                            }&lt;br /&gt;
                        )&lt;br /&gt;
                    )&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            const card =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;div&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            card.className =&lt;br /&gt;
                &#039;stall-product-group-card&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 商品名&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const heading =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;h3&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            heading.className =&lt;br /&gt;
                &#039;stall-product-group-name&#039;;&lt;br /&gt;
&lt;br /&gt;
            heading.textContent =&lt;br /&gt;
                group.menuName +&lt;br /&gt;
                &#039; / &#039; +&lt;br /&gt;
                group.unit;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            card.appendChild(&lt;br /&gt;
                heading&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 比較店舗数&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const count =&lt;br /&gt;
                document.createElement(&lt;br /&gt;
                    &#039;div&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            count.className =&lt;br /&gt;
                &#039;stall-product-group-count&#039;;&lt;br /&gt;
&lt;br /&gt;
            count.textContent =&lt;br /&gt;
                &#039;比較店舗：&#039; +&lt;br /&gt;
                placementIds.length +&lt;br /&gt;
                &#039;店&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            card.appendChild(&lt;br /&gt;
                count&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
/* =============================&lt;br /&gt;
 * 対象店舗リンク&lt;br /&gt;
 * ============================= */&lt;br /&gt;
&lt;br /&gt;
const stallList =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
stallList.className =&lt;br /&gt;
    &#039;stall-product-group-stalls&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const stallListLabel =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;span&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
stallListLabel.className =&lt;br /&gt;
    &#039;stall-product-group-label&#039;;&lt;br /&gt;
&lt;br /&gt;
stallListLabel.textContent =&lt;br /&gt;
    &#039;対象店舗：&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
stallList.appendChild(&lt;br /&gt;
    stallListLabel&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 屋台名をリンクとして追加&lt;br /&gt;
 */&lt;br /&gt;
appendStallLinks(&lt;br /&gt;
    stallList,&lt;br /&gt;
    items&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
card.appendChild(&lt;br /&gt;
    stallList&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 1店舗しかない場合&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                placementIds.length &amp;lt; 2&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const notice =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                notice.className =&lt;br /&gt;
                    &#039;stall-product-group-notice&#039;;&lt;br /&gt;
&lt;br /&gt;
                notice.textContent =&lt;br /&gt;
                    &#039;比較対象が1店舗のみです。&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                card.appendChild(&lt;br /&gt;
                    notice&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 最安価格の商品&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const lowestPriceItems =&lt;br /&gt;
                items.filter(&lt;br /&gt;
                    function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            item.menu&lt;br /&gt;
                                .isLowestPrice ===&lt;br /&gt;
                            true&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                lowestPriceItems.length &amp;gt; 0&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const lowestPrice =&lt;br /&gt;
                    lowestPriceItems[&lt;br /&gt;
                        0&lt;br /&gt;
                    ].menu.priceValue;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const row =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                row.className =&lt;br /&gt;
                    &#039;stall-product-group-best&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const label =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;span&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                label.className =&lt;br /&gt;
                    &#039;stall-product-group-label&#039;;&lt;br /&gt;
&lt;br /&gt;
                label.textContent =&lt;br /&gt;
                    &#039;最安価格：&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const value =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;strong&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                value.textContent =&lt;br /&gt;
                    lowestPrice +&lt;br /&gt;
                    &#039;円&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                row.appendChild(&lt;br /&gt;
                    label&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                row.appendChild(&lt;br /&gt;
                    value&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                card.appendChild(&lt;br /&gt;
                    row&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
 * 最安店舗リンク&lt;br /&gt;
 */&lt;br /&gt;
const shopRow =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;div&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
shopRow.className =&lt;br /&gt;
    &#039;stall-product-group-shop&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
const shopLabel =&lt;br /&gt;
    document.createElement(&lt;br /&gt;
        &#039;span&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
shopLabel.className =&lt;br /&gt;
    &#039;stall-product-group-label&#039;;&lt;br /&gt;
&lt;br /&gt;
shopLabel.textContent =&lt;br /&gt;
    &#039;最安：&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
shopRow.appendChild(&lt;br /&gt;
    shopLabel&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 最安店舗をリンク表示&lt;br /&gt;
 */&lt;br /&gt;
appendStallLinks(&lt;br /&gt;
    shopRow,&lt;br /&gt;
    lowestPriceItems&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
card.appendChild(&lt;br /&gt;
    shopRow&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            /* =============================&lt;br /&gt;
             * 最安単位価格&lt;br /&gt;
             * ============================= */&lt;br /&gt;
&lt;br /&gt;
            const lowestUnitItems =&lt;br /&gt;
                items.filter(&lt;br /&gt;
                    function ( item ) {&lt;br /&gt;
&lt;br /&gt;
                        return (&lt;br /&gt;
                            item.menu&lt;br /&gt;
                                .isLowestUnitPrice ===&lt;br /&gt;
                            true&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                lowestUnitItems.length &amp;gt; 0&lt;br /&gt;
            ) {&lt;br /&gt;
&lt;br /&gt;
                const unitPrice =&lt;br /&gt;
                    lowestUnitItems[&lt;br /&gt;
                        0&lt;br /&gt;
                    ].menu.unitPriceValue;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * 小数表示調整&lt;br /&gt;
                 */&lt;br /&gt;
                const displayUnitPrice =&lt;br /&gt;
                    Math.round(&lt;br /&gt;
                        unitPrice *&lt;br /&gt;
                        100&lt;br /&gt;
                    ) /&lt;br /&gt;
                    100;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const row =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;div&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                row.className =&lt;br /&gt;
                    &#039;stall-product-group-best-unit&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const label =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;span&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                label.className =&lt;br /&gt;
                    &#039;stall-product-group-label&#039;;&lt;br /&gt;
&lt;br /&gt;
                label.textContent =&lt;br /&gt;
                    &#039;最安単位価格：&#039;;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                const value =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;strong&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                value.textContent =&lt;br /&gt;
                    displayUnitPrice +&lt;br /&gt;
                    &#039;円/&#039; +&lt;br /&gt;
                    group.unit;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                row.appendChild(&lt;br /&gt;
                    label&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                row.appendChild(&lt;br /&gt;
                    value&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
                card.appendChild(&lt;br /&gt;
                    row&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            summary.appendChild(&lt;br /&gt;
                card&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 比較表の一番上へ追加&lt;br /&gt;
     */&lt;br /&gt;
    comparePage.insertBefore(&lt;br /&gt;
        summary,&lt;br /&gt;
        comparePage.firstChild&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =================================&lt;br /&gt;
 * 最安値を自動判定&lt;br /&gt;
 * ================================= */&lt;br /&gt;
&lt;br /&gt;
markBestPrices(&lt;br /&gt;
    compareData&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
renderComparison(&lt;br /&gt;
    compareData&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 詳細比較表を描画した後に&lt;br /&gt;
 * 商品別サマリーを追加&lt;br /&gt;
 */&lt;br /&gt;
renderProductGroupSummary(&lt;br /&gt;
    compareData&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    ).catch(&lt;br /&gt;
        function ( error ) {&lt;br /&gt;
&lt;br /&gt;
            console.error(&lt;br /&gt;
                &#039;Placement比較データ取得エラー:&#039;,&lt;br /&gt;
                error&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            renderMessage(&lt;br /&gt;
                &#039;比較データの取得中にエラーが発生しました。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
const statusLabels = {&lt;br /&gt;
    active: &#039;出店中・出店予定&#039;,&lt;br /&gt;
    cancelled: &#039;出店中止&#039;,&lt;br /&gt;
    unknown: &#039;未確認&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
    const statusSelect = document.querySelector(&lt;br /&gt;
        &#039;select[name=&amp;quot;FestivalStallPlacement[status]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (statusSelect) {&lt;br /&gt;
        Array.from(statusSelect.options).forEach(function (option) {&lt;br /&gt;
            if (statusLabels[option.value]) {&lt;br /&gt;
                option.textContent = statusLabels[option.value];&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const verificationLabels = {&lt;br /&gt;
        verified: &#039;確認済み&#039;,&lt;br /&gt;
        partially_verified: &#039;一部確認済み&#039;,&lt;br /&gt;
        unverified: &#039;未確認&#039;,&lt;br /&gt;
        outdated: &#039;情報が古い可能性あり&#039;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    const verificationSelect = document.querySelector(&lt;br /&gt;
        &#039;select[name=&amp;quot;FestivalStallPlacement[verification_status]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (verificationSelect) {&lt;br /&gt;
        Array.from(verificationSelect.options).forEach(function (option) {&lt;br /&gt;
            if (verificationLabels[option.value]) {&lt;br /&gt;
                option.textContent = verificationLabels[option.value];&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    const yearInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[year]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
if (yearInput) {&lt;br /&gt;
    yearInput.inputMode = &#039;numeric&#039;;&lt;br /&gt;
    yearInput.maxLength = 4;&lt;br /&gt;
&lt;br /&gt;
    const validateYear = function () {&lt;br /&gt;
        const value = yearInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        if (value !== &#039;&#039; &amp;amp;&amp;amp; !/^\d{4}$/.test(value)) {&lt;br /&gt;
            yearInput.setCustomValidity(&lt;br /&gt;
                &#039;開催年は4桁の数字で入力してください（例：2026）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            yearInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    yearInput.addEventListener(&#039;input&#039;, validateYear);&lt;br /&gt;
    yearInput.addEventListener(&#039;change&#039;, validateYear);&lt;br /&gt;
    yearInput.addEventListener(&#039;invalid&#039;, validateYear);&lt;br /&gt;
&lt;br /&gt;
    validateYear();&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
const positionLabels = {&lt;br /&gt;
    exact: &#039;位置確認済み&#039;,&lt;br /&gt;
    approximate: &#039;おおよその位置&#039;,&lt;br /&gt;
    unknown: &#039;位置未確認&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
const positionSelect = document.querySelector(&lt;br /&gt;
    &#039;select[name=&amp;quot;FestivalStallPlacement[position_status]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
if (positionSelect) {&lt;br /&gt;
    Array.from(positionSelect.options).forEach(function (option) {&lt;br /&gt;
        if (positionLabels[option.value]) {&lt;br /&gt;
            option.textContent = positionLabels[option.value];&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
    const accuracyInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[position_accuracy_m]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
if (accuracyInput) {&lt;br /&gt;
    accuracyInput.inputMode = &#039;numeric&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateAccuracy = function () {&lt;br /&gt;
        const value = accuracyInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        if (value !== &#039;&#039; &amp;amp;&amp;amp; !/^\d+$/.test(value)) {&lt;br /&gt;
            accuracyInput.setCustomValidity(&lt;br /&gt;
                &#039;位置精度は0以上の整数で入力してください（例：10）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            accuracyInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    accuracyInput.addEventListener(&#039;input&#039;, validateAccuracy);&lt;br /&gt;
    accuracyInput.addEventListener(&#039;change&#039;, validateAccuracy);&lt;br /&gt;
    accuracyInput.addEventListener(&#039;invalid&#039;, validateAccuracy);&lt;br /&gt;
&lt;br /&gt;
    validateAccuracy();&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
    const openingTimeInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[opening_time]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
const closingTimeInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[closing_time]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
const timePattern = /^([01]\d|2[0-3]):[0-5]\d$/;&lt;br /&gt;
&lt;br /&gt;
function setupTimeValidation(input, label) {&lt;br /&gt;
    if (!input) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.placeholder = &#039;例：10:00&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateTime = function () {&lt;br /&gt;
        const value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (value !== &#039;&#039; &amp;amp;&amp;amp; !timePattern.test(value)) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                label + &#039;は24時間表記の HH:MM 形式で入力してください（例：10:00）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    input.addEventListener(&#039;input&#039;, validateTime);&lt;br /&gt;
    input.addEventListener(&#039;change&#039;, validateTime);&lt;br /&gt;
    input.addEventListener(&#039;invalid&#039;, validateTime);&lt;br /&gt;
&lt;br /&gt;
    validateTime();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
setupTimeValidation(openingTimeInput, &#039;営業開始時刻&#039;);&lt;br /&gt;
setupTimeValidation(closingTimeInput, &#039;営業終了時刻&#039;);&lt;br /&gt;
    &lt;br /&gt;
    const latitudeInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[latitude]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
const longitudeInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[longitude]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
function setupCoordinateValidation(input, label, min, max) {&lt;br /&gt;
    if (!input) {&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.inputMode = &#039;decimal&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateCoordinate = function () {&lt;br /&gt;
        const value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * exact または approximate の場合は&lt;br /&gt;
         * 緯度・経度を必須にする。&lt;br /&gt;
         */&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            if (&lt;br /&gt;
                positionSelect &amp;amp;&amp;amp;&lt;br /&gt;
                (&lt;br /&gt;
                    positionSelect.value === &#039;exact&#039; ||&lt;br /&gt;
                    positionSelect.value === &#039;approximate&#039;&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    label +&lt;br /&gt;
                    &#039;は「位置確認済み」または「おおよその位置」を選択した場合は必須です。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const otherInput =&lt;br /&gt;
                input === latitudeInput&lt;br /&gt;
                    ? longitudeInput&lt;br /&gt;
                    : latitudeInput;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                otherInput &amp;amp;&amp;amp;&lt;br /&gt;
                otherInput.value.trim() !== &#039;&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    &#039;緯度と経度は両方入力するか、両方空欄にしてください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 数値形式チェック&lt;br /&gt;
         */&lt;br /&gt;
        if (!/^-?\d+(\.\d+)?$/.test(value)) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                label + &#039;は数値で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 範囲チェック&lt;br /&gt;
         */&lt;br /&gt;
        const number = Number(value);&lt;br /&gt;
&lt;br /&gt;
        if (number &amp;lt; min || number &amp;gt; max) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                label +&lt;br /&gt;
                &#039;は&#039; +&lt;br /&gt;
                min +&lt;br /&gt;
                &#039;〜&#039; +&lt;br /&gt;
                max +&lt;br /&gt;
                &#039;の範囲で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    input.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateCoordinate&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    input.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateCoordinate&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    input.addEventListener(&lt;br /&gt;
        &#039;invalid&#039;,&lt;br /&gt;
        validateCoordinate&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    validateCoordinate();&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * position_status変更時に&lt;br /&gt;
     * 再チェックできるよう関数を返す。&lt;br /&gt;
     */&lt;br /&gt;
    return validateCoordinate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
const validateLatitude =&lt;br /&gt;
    setupCoordinateValidation(&lt;br /&gt;
        latitudeInput,&lt;br /&gt;
        &#039;緯度&#039;,&lt;br /&gt;
        20,&lt;br /&gt;
        46&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
const validateLongitude =&lt;br /&gt;
    setupCoordinateValidation(&lt;br /&gt;
        longitudeInput,&lt;br /&gt;
        &#039;経度&#039;,&lt;br /&gt;
        122,&lt;br /&gt;
        154&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 一方の座標を変更した場合、&lt;br /&gt;
 * 反対側のペア整合性も再検証する。&lt;br /&gt;
 */&lt;br /&gt;
if (&lt;br /&gt;
    latitudeInput &amp;amp;&amp;amp;&lt;br /&gt;
    validateLongitude&lt;br /&gt;
) {&lt;br /&gt;
    latitudeInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateLongitude&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    latitudeInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateLongitude&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
    longitudeInput &amp;amp;&amp;amp;&lt;br /&gt;
    validateLatitude&lt;br /&gt;
) {&lt;br /&gt;
    longitudeInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateLatitude&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    longitudeInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateLatitude&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 位置情報の状態を変更した場合、&lt;br /&gt;
 * 緯度・経度を再検証する。&lt;br /&gt;
 */&lt;br /&gt;
if (positionSelect) {&lt;br /&gt;
    positionSelect.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        function () {&lt;br /&gt;
            if (validateLatitude) {&lt;br /&gt;
                validateLatitude();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (validateLongitude) {&lt;br /&gt;
                validateLongitude();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
    const sourceUrlInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[source_url]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
if (sourceUrlInput) {&lt;br /&gt;
    sourceUrlInput.inputMode = &#039;url&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateSourceUrl = function () {&lt;br /&gt;
        const value = sourceUrlInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        sourceUrlInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(value);&lt;br /&gt;
&lt;br /&gt;
            if (url.protocol !== &#039;http:&#039; &amp;amp;&amp;amp; url.protocol !== &#039;https:&#039;) {&lt;br /&gt;
                sourceUrlInput.setCustomValidity(&lt;br /&gt;
                    &#039;情報元URLは http:// または https:// で始まるURLを入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        } catch (e) {&lt;br /&gt;
            sourceUrlInput.setCustomValidity(&lt;br /&gt;
                &#039;情報元URLを正しいURL形式で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    sourceUrlInput.addEventListener(&#039;input&#039;, validateSourceUrl);&lt;br /&gt;
    sourceUrlInput.addEventListener(&#039;change&#039;, validateSourceUrl);&lt;br /&gt;
    sourceUrlInput.addEventListener(&#039;invalid&#039;, validateSourceUrl);&lt;br /&gt;
&lt;br /&gt;
    validateSourceUrl();&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
    const sortOrderInput = document.querySelector(&lt;br /&gt;
    &#039;input[name=&amp;quot;FestivalStallPlacement[sort_order]&amp;quot;]&#039;&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
if (sortOrderInput) {&lt;br /&gt;
    sortOrderInput.inputMode = &#039;numeric&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateSortOrder = function () {&lt;br /&gt;
        const value = sortOrderInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        sortOrderInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (value !== &#039;&#039; &amp;amp;&amp;amp; !/^\d+$/.test(value)) {&lt;br /&gt;
            sortOrderInput.setCustomValidity(&lt;br /&gt;
                &#039;表示順は0以上の整数で入力してください（例：1）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    sortOrderInput.addEventListener(&#039;input&#039;, validateSortOrder);&lt;br /&gt;
    sortOrderInput.addEventListener(&#039;change&#039;, validateSortOrder);&lt;br /&gt;
    sortOrderInput.addEventListener(&#039;invalid&#039;, validateSortOrder);&lt;br /&gt;
&lt;br /&gt;
    validateSortOrder();&lt;br /&gt;
}&lt;br /&gt;
    &lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * FestivalStallPlacement - 最終確認日の未来日チェック&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
	&#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
	function setupLastConfirmedValidation() {&lt;br /&gt;
		const dateInputs = document.querySelectorAll(&lt;br /&gt;
			&#039;input[name=&amp;quot;FestivalStallPlacement[last_confirmed]&amp;quot;]&#039;&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		dateInputs.forEach(function (dateInput) {&lt;br /&gt;
			if (dateInput.dataset.lastConfirmedValidation === &#039;1&#039;) {&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			dateInput.dataset.lastConfirmedValidation = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
			function getVisibleInput() {&lt;br /&gt;
				const widget = dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
				if (!widget) {&lt;br /&gt;
					return null;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				return widget.querySelector(&#039;input[type=&amp;quot;text&amp;quot;]&#039;);&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			function getErrorElement() {&lt;br /&gt;
				const widget = dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
				if (!widget) {&lt;br /&gt;
					return null;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				let error = widget.parentNode.querySelector(&lt;br /&gt;
					&#039;.stall-last-confirmed-error&#039;&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
				if (!error) {&lt;br /&gt;
					error = document.createElement(&#039;div&#039;);&lt;br /&gt;
					error.className = &#039;stall-last-confirmed-error&#039;;&lt;br /&gt;
					error.setAttribute(&#039;role&#039;, &#039;alert&#039;);&lt;br /&gt;
					error.hidden = true;&lt;br /&gt;
&lt;br /&gt;
					widget.insertAdjacentElement(&#039;afterend&#039;, error);&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				return error;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			function showError() {&lt;br /&gt;
				const visibleInput = getVisibleInput();&lt;br /&gt;
				const error = getErrorElement();&lt;br /&gt;
&lt;br /&gt;
				if (!visibleInput || !error) {&lt;br /&gt;
					return;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				let message;&lt;br /&gt;
&lt;br /&gt;
				if (dateInput.validity.rangeOverflow) {&lt;br /&gt;
					const maxDate = dateInput.max.replace(/-/g, &#039;/&#039;);&lt;br /&gt;
&lt;br /&gt;
					message =&lt;br /&gt;
						&#039;未来の日付は入力できません。&#039; +&lt;br /&gt;
						maxDate +&lt;br /&gt;
						&#039;以前の日付を入力してください。&#039;;&lt;br /&gt;
				} else {&lt;br /&gt;
					message =&lt;br /&gt;
						dateInput.validationMessage ||&lt;br /&gt;
						&#039;正しい日付を入力してください。&#039;;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				error.textContent = message;&lt;br /&gt;
				error.hidden = false;&lt;br /&gt;
&lt;br /&gt;
				visibleInput.setAttribute(&#039;aria-invalid&#039;, &#039;true&#039;);&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			function clearError() {&lt;br /&gt;
				const visibleInput = getVisibleInput();&lt;br /&gt;
				const error = getErrorElement();&lt;br /&gt;
&lt;br /&gt;
				if (error) {&lt;br /&gt;
					error.hidden = true;&lt;br /&gt;
					error.textContent = &#039;&#039;;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				if (visibleInput) {&lt;br /&gt;
					visibleInput.removeAttribute(&#039;aria-invalid&#039;);&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			/*&lt;br /&gt;
			 * 非表示の date input に対する&lt;br /&gt;
			 * ブラウザ標準エラー表示を止める。&lt;br /&gt;
			 */&lt;br /&gt;
			dateInput.addEventListener(&#039;invalid&#039;, function (event) {&lt;br /&gt;
				event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
				showError();&lt;br /&gt;
&lt;br /&gt;
				const visibleInput = getVisibleInput();&lt;br /&gt;
&lt;br /&gt;
				if (visibleInput) {&lt;br /&gt;
					window.setTimeout(function () {&lt;br /&gt;
						visibleInput.focus();&lt;br /&gt;
					}, 0);&lt;br /&gt;
				}&lt;br /&gt;
			});&lt;br /&gt;
&lt;br /&gt;
			/*&lt;br /&gt;
			 * ユーザーが日付を修正したら&lt;br /&gt;
			 * 有効になった時点でエラーを消す。&lt;br /&gt;
			 */&lt;br /&gt;
const form = dateInput.form;&lt;br /&gt;
&lt;br /&gt;
if (form) {&lt;br /&gt;
    function handleDateChange(event) {&lt;br /&gt;
        const currentWidget =&lt;br /&gt;
            dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !currentWidget ||&lt;br /&gt;
            !currentWidget.contains(event.target)&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (dateInput.validity.valid) {&lt;br /&gt;
                clearError();&lt;br /&gt;
            } else if (&lt;br /&gt;
                dateInput.validity.rangeOverflow&lt;br /&gt;
            ) {&lt;br /&gt;
                showError();&lt;br /&gt;
            }&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    form.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        handleDateChange&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    form.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        handleDateChange&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Page Forms のカレンダー選択では&lt;br /&gt;
     * visible input に blur が発生する。&lt;br /&gt;
     * blur は通常バブルしないため capture=true。&lt;br /&gt;
     */&lt;br /&gt;
    form.addEventListener(&lt;br /&gt;
        &#039;blur&#039;,&lt;br /&gt;
        handleDateChange,&lt;br /&gt;
        true&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
		});&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
		document.addEventListener(&lt;br /&gt;
			&#039;DOMContentLoaded&#039;,&lt;br /&gt;
			setupLastConfirmedValidation&lt;br /&gt;
		);&lt;br /&gt;
	} else {&lt;br /&gt;
		setupLastConfirmedValidation();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
		setupLastConfirmedValidation();&lt;br /&gt;
	});&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * FestivalStallPlacement&lt;br /&gt;
 * Cargo既存レコード候補警告 V2&lt;br /&gt;
 *&lt;br /&gt;
 * 同じ festival + year + venue + stall があれば&lt;br /&gt;
 * 警告と既存ページへのリンクを表示する。&lt;br /&gt;
 * 保存自体は禁止しない。&lt;br /&gt;
 */&lt;br /&gt;
mw.loader.using([&lt;br /&gt;
	&#039;mediawiki.api&#039;,&lt;br /&gt;
	&#039;mediawiki.util&#039;&lt;br /&gt;
]).then(function () {&lt;br /&gt;
	&#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
	const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
	function setupDuplicateWarning() {&lt;br /&gt;
		const form = document.getElementById(&#039;pfForm&#039;);&lt;br /&gt;
&lt;br /&gt;
		if (!form) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if (form.dataset.duplicateWarningV2 === &#039;1&#039;) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		const table = form.querySelector(&#039;.formtable&#039;);&lt;br /&gt;
&lt;br /&gt;
		if (!table) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		form.dataset.duplicateWarningV2 = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
		const warning = document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
		warning.className = &#039;stall-duplicate-warning&#039;;&lt;br /&gt;
		warning.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
		warning.hidden = true;&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 表の中ではなく、表の直前に置く。&lt;br /&gt;
		 * 警告表示でフォームの列幅を崩さない。&lt;br /&gt;
		 */&lt;br /&gt;
		table.insertAdjacentElement(&#039;beforebegin&#039;, warning);&lt;br /&gt;
&lt;br /&gt;
		let timer = null;&lt;br /&gt;
		let requestId = 0;&lt;br /&gt;
&lt;br /&gt;
		function escapeCargo(value) {&lt;br /&gt;
			return String(value).replace(/&#039;/g, &amp;quot;&#039;&#039;&amp;quot;);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function getField(name) {&lt;br /&gt;
			return form.querySelector(&lt;br /&gt;
				&#039;[name=&amp;quot;FestivalStallPlacement[&#039; +&lt;br /&gt;
				name +&lt;br /&gt;
				&#039;]&amp;quot;]&#039;&lt;br /&gt;
			);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function cargoQuery(tableName, fields, where, limit) {&lt;br /&gt;
			return api.get({&lt;br /&gt;
				action: &#039;cargoquery&#039;,&lt;br /&gt;
				tables: tableName,&lt;br /&gt;
				fields: fields,&lt;br /&gt;
				where: where,&lt;br /&gt;
				limit: limit || 50,&lt;br /&gt;
				format: &#039;json&#039;&lt;br /&gt;
			}).then(function (data) {&lt;br /&gt;
				if (&lt;br /&gt;
					!data ||&lt;br /&gt;
					!Array.isArray(data.cargoquery)&lt;br /&gt;
				) {&lt;br /&gt;
					return [];&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				return data.cargoquery.map(function (item) {&lt;br /&gt;
					return item.title || item;&lt;br /&gt;
				});&lt;br /&gt;
			});&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function resolveId(&lt;br /&gt;
			tableName,&lt;br /&gt;
			idField,&lt;br /&gt;
			nameField,&lt;br /&gt;
			value&lt;br /&gt;
		) {&lt;br /&gt;
			if (!value) {&lt;br /&gt;
				return Promise.resolve(null);&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			if (/^\d+$/.test(value)) {&lt;br /&gt;
				return Promise.resolve(value);&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			return cargoQuery(&lt;br /&gt;
				tableName,&lt;br /&gt;
				idField + &#039;=resolved_id&#039;,&lt;br /&gt;
				nameField +&lt;br /&gt;
					&amp;quot;=&#039;&amp;quot; +&lt;br /&gt;
					escapeCargo(value) +&lt;br /&gt;
					&amp;quot;&#039;&amp;quot;,&lt;br /&gt;
				2&lt;br /&gt;
			).then(function (rows) {&lt;br /&gt;
				if (rows.length !== 1) {&lt;br /&gt;
					console.warn(&lt;br /&gt;
						&#039;IDを一意に取得できません:&#039;,&lt;br /&gt;
						tableName,&lt;br /&gt;
						value,&lt;br /&gt;
						rows&lt;br /&gt;
					);&lt;br /&gt;
&lt;br /&gt;
					return null;&lt;br /&gt;
				}&lt;br /&gt;
&lt;br /&gt;
				return String(rows[0].resolved_id);&lt;br /&gt;
			});&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function clearWarning() {&lt;br /&gt;
			warning.hidden = true;&lt;br /&gt;
			warning.replaceChildren();&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function showFailure() {&lt;br /&gt;
			warning.replaceChildren();&lt;br /&gt;
&lt;br /&gt;
			const text = document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
			text.textContent =&lt;br /&gt;
				&#039;既存データの確認に失敗しました。&#039; +&lt;br /&gt;
				&#039;登録はできますが、重複がないかご確認ください。&#039;;&lt;br /&gt;
&lt;br /&gt;
			warning.appendChild(text);&lt;br /&gt;
			warning.hidden = false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
function showCandidates(rows, venueSpecified) {&lt;br /&gt;
	warning.replaceChildren();&lt;br /&gt;
&lt;br /&gt;
const positionLabels = {&lt;br /&gt;
    exact: &#039;位置確認済み&#039;,&lt;br /&gt;
    approximate: &#039;おおよその位置&#039;,&lt;br /&gt;
    unknown: &#039;位置未確認&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
	const verificationLabels = {&lt;br /&gt;
		verified: &#039;確認済み&#039;,&lt;br /&gt;
		partially_verified: &#039;一部確認済み&#039;,&lt;br /&gt;
		unverified: &#039;未確認&#039;,&lt;br /&gt;
		outdated: &#039;情報が古い可能性あり&#039;&lt;br /&gt;
	};&lt;br /&gt;
	&lt;br /&gt;
const statusLabels = {&lt;br /&gt;
    active: &#039;出店中・出店予定&#039;,&lt;br /&gt;
    cancelled: &#039;出店中止&#039;,&lt;br /&gt;
    unknown: &#039;未確認&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
	function displayValue(value, fallback) {&lt;br /&gt;
		if (&lt;br /&gt;
			value === undefined ||&lt;br /&gt;
			value === null ||&lt;br /&gt;
			String(value).trim() === &#039;&#039;&lt;br /&gt;
		) {&lt;br /&gt;
			return fallback || &#039;未確認&#039;;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return String(value);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function addDetail(container, label, value) {&lt;br /&gt;
		const row = document.createElement(&#039;div&#039;);&lt;br /&gt;
		row.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate-detail&#039;;&lt;br /&gt;
&lt;br /&gt;
		const labelElement =&lt;br /&gt;
			document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
		labelElement.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate-label&#039;;&lt;br /&gt;
&lt;br /&gt;
		labelElement.textContent = label;&lt;br /&gt;
&lt;br /&gt;
		const valueElement =&lt;br /&gt;
			document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
		valueElement.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate-value&#039;;&lt;br /&gt;
&lt;br /&gt;
		valueElement.textContent = value;&lt;br /&gt;
&lt;br /&gt;
		row.appendChild(labelElement);&lt;br /&gt;
		row.appendChild(valueElement);&lt;br /&gt;
&lt;br /&gt;
		container.appendChild(row);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	const title = document.createElement(&#039;strong&#039;);&lt;br /&gt;
&lt;br /&gt;
	title.className =&lt;br /&gt;
		&#039;stall-duplicate-warning-title&#039;;&lt;br /&gt;
&lt;br /&gt;
	title.textContent =&lt;br /&gt;
		venueSpecified&lt;br /&gt;
			? (&lt;br /&gt;
				&#039;⚠ 同じ祭り・開催年・会場・屋台の既存データが&#039; +&lt;br /&gt;
				rows.length +&lt;br /&gt;
				&#039;件あります。&#039;&lt;br /&gt;
			)&lt;br /&gt;
			: (&lt;br /&gt;
				&#039;⚠ 同じ祭り・開催年・屋台の既存データが&#039; +&lt;br /&gt;
				rows.length +&lt;br /&gt;
				&#039;件あります。&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
	warning.appendChild(title);&lt;br /&gt;
&lt;br /&gt;
	const description =&lt;br /&gt;
		document.createElement(&#039;p&#039;);&lt;br /&gt;
&lt;br /&gt;
	description.className =&lt;br /&gt;
		&#039;stall-duplicate-warning-description&#039;;&lt;br /&gt;
&lt;br /&gt;
	description.textContent =&lt;br /&gt;
		venueSpecified&lt;br /&gt;
			? (&lt;br /&gt;
				&#039;出店場所が異なる場合は新規登録して構いません。&#039; +&lt;br /&gt;
				&#039;下の既存データと同じ場所ではないか確認してください。&#039;&lt;br /&gt;
			)&lt;br /&gt;
			: (&lt;br /&gt;
				&#039;会場未指定のため、会場を問わず候補を確認しています。&#039; +&lt;br /&gt;
				&#039;出店場所が異なる場合は新規登録して構いません。&#039; +&lt;br /&gt;
				&#039;下の既存データと同じ場所ではないか確認してください。&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
	warning.appendChild(description);&lt;br /&gt;
&lt;br /&gt;
	const list = document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
	list.className =&lt;br /&gt;
		&#039;stall-duplicate-candidate-list&#039;;&lt;br /&gt;
&lt;br /&gt;
	/*&lt;br /&gt;
	 * placement_id順に並べる&lt;br /&gt;
	 */&lt;br /&gt;
	rows.sort(function (a, b) {&lt;br /&gt;
		return (&lt;br /&gt;
			Number(a.placement_id) -&lt;br /&gt;
			Number(b.placement_id)&lt;br /&gt;
		);&lt;br /&gt;
	});&lt;br /&gt;
&lt;br /&gt;
	rows.forEach(function (row) {&lt;br /&gt;
		const card =&lt;br /&gt;
			document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
		card.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate&#039;;&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * カード見出し&lt;br /&gt;
		 */&lt;br /&gt;
		const header =&lt;br /&gt;
			document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
		header.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate-header&#039;;&lt;br /&gt;
&lt;br /&gt;
const heading =&lt;br /&gt;
	document.createElement(&#039;strong&#039;);&lt;br /&gt;
&lt;br /&gt;
heading.textContent =&lt;br /&gt;
	&#039;既存の出店情報&#039;;&lt;br /&gt;
&lt;br /&gt;
header.appendChild(heading);&lt;br /&gt;
&lt;br /&gt;
		card.appendChild(header);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 出店場所&lt;br /&gt;
		 */&lt;br /&gt;
		addDetail(&lt;br /&gt;
			card,&lt;br /&gt;
			&#039;出店場所&#039;,&lt;br /&gt;
			displayValue(&lt;br /&gt;
				row.location_note,&lt;br /&gt;
				&#039;場所メモなし&#039;&lt;br /&gt;
			)&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 位置状態&lt;br /&gt;
		 */&lt;br /&gt;
		addDetail(&lt;br /&gt;
			card,&lt;br /&gt;
			&#039;位置状態&#039;,&lt;br /&gt;
			positionLabels[&lt;br /&gt;
				row.position_status&lt;br /&gt;
			] ||&lt;br /&gt;
			displayValue(&lt;br /&gt;
				row.position_status,&lt;br /&gt;
				&#039;位置未確認&#039;&lt;br /&gt;
			)&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 緯度・経度&lt;br /&gt;
		 */&lt;br /&gt;
		let coordinates =&lt;br /&gt;
			&#039;位置情報なし&#039;;&lt;br /&gt;
&lt;br /&gt;
		if (&lt;br /&gt;
			row.latitude !== undefined &amp;amp;&amp;amp;&lt;br /&gt;
			row.latitude !== null &amp;amp;&amp;amp;&lt;br /&gt;
			String(row.latitude).trim() !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
			row.longitude !== undefined &amp;amp;&amp;amp;&lt;br /&gt;
			row.longitude !== null &amp;amp;&amp;amp;&lt;br /&gt;
			String(row.longitude).trim() !== &#039;&#039;&lt;br /&gt;
		) {&lt;br /&gt;
			coordinates =&lt;br /&gt;
				String(row.latitude) +&lt;br /&gt;
				&#039;, &#039; +&lt;br /&gt;
				String(row.longitude);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		addDetail(&lt;br /&gt;
			card,&lt;br /&gt;
			&#039;緯度・経度&#039;,&lt;br /&gt;
			coordinates&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 位置精度&lt;br /&gt;
 */&lt;br /&gt;
let accuracy = &#039;未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
	row.position_accuracy_m !== undefined &amp;amp;&amp;amp;&lt;br /&gt;
	row.position_accuracy_m !== null &amp;amp;&amp;amp;&lt;br /&gt;
	String(row.position_accuracy_m).trim() !== &#039;&#039;&lt;br /&gt;
) {&lt;br /&gt;
	accuracy =&lt;br /&gt;
		String(row.position_accuracy_m) +&lt;br /&gt;
		&#039; m&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
addDetail(&lt;br /&gt;
	card,&lt;br /&gt;
	&#039;位置精度&#039;,&lt;br /&gt;
	accuracy&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 出店状態&lt;br /&gt;
 */&lt;br /&gt;
addDetail(&lt;br /&gt;
	card,&lt;br /&gt;
	&#039;出店状態&#039;,&lt;br /&gt;
	statusLabels[&lt;br /&gt;
		row.status&lt;br /&gt;
	] ||&lt;br /&gt;
	displayValue(&lt;br /&gt;
		row.status,&lt;br /&gt;
		&#039;未確認&#039;&lt;br /&gt;
	)&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 最終確認日&lt;br /&gt;
 */&lt;br /&gt;
let lastConfirmed = &#039;未確認&#039;;&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
	row.last_confirmed !== undefined &amp;amp;&amp;amp;&lt;br /&gt;
	row.last_confirmed !== null &amp;amp;&amp;amp;&lt;br /&gt;
	String(row.last_confirmed).trim() !== &#039;&#039;&lt;br /&gt;
) {&lt;br /&gt;
	lastConfirmed =&lt;br /&gt;
		String(row.last_confirmed)&lt;br /&gt;
			.replace(/-/g, &#039;/&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
addDetail(&lt;br /&gt;
	card,&lt;br /&gt;
	&#039;最終確認日&#039;,&lt;br /&gt;
	lastConfirmed&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 確認状態&lt;br /&gt;
		 */&lt;br /&gt;
		addDetail(&lt;br /&gt;
			card,&lt;br /&gt;
			&#039;確認状態&#039;,&lt;br /&gt;
			verificationLabels[&lt;br /&gt;
				row.verification_status&lt;br /&gt;
			] ||&lt;br /&gt;
			displayValue(&lt;br /&gt;
				row.verification_status,&lt;br /&gt;
				&#039;未確認&#039;&lt;br /&gt;
			)&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 既存ページへのリンク&lt;br /&gt;
		 */&lt;br /&gt;
		const actions =&lt;br /&gt;
			document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
		actions.className =&lt;br /&gt;
			&#039;stall-duplicate-candidate-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
		const link =&lt;br /&gt;
			document.createElement(&#039;a&#039;);&lt;br /&gt;
&lt;br /&gt;
		link.href =&lt;br /&gt;
			mw.util.getUrl(row.page_name);&lt;br /&gt;
&lt;br /&gt;
		link.target = &#039;_blank&#039;;&lt;br /&gt;
		link.rel = &#039;noopener&#039;;&lt;br /&gt;
&lt;br /&gt;
		link.textContent =&lt;br /&gt;
			&#039;既存データを確認&#039;;&lt;br /&gt;
&lt;br /&gt;
		actions.appendChild(link);&lt;br /&gt;
		card.appendChild(actions);&lt;br /&gt;
&lt;br /&gt;
		list.appendChild(card);&lt;br /&gt;
	});&lt;br /&gt;
&lt;br /&gt;
	warning.appendChild(list);&lt;br /&gt;
&lt;br /&gt;
	const footer =&lt;br /&gt;
		document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
	footer.className =&lt;br /&gt;
		&#039;stall-duplicate-warning-footer&#039;;&lt;br /&gt;
&lt;br /&gt;
	footer.textContent =&lt;br /&gt;
		&#039;同じ場所の場合は新規登録せず、既存データを編集することをおすすめします。&#039;;&lt;br /&gt;
&lt;br /&gt;
	warning.appendChild(footer);&lt;br /&gt;
&lt;br /&gt;
	warning.hidden = false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function checkDuplicates() {&lt;br /&gt;
	const currentRequest = ++requestId;&lt;br /&gt;
&lt;br /&gt;
	/*&lt;br /&gt;
	 * 毎回現在のinput/selectを取得する。&lt;br /&gt;
	 * Page Formsが要素を作り直しても対応できる。&lt;br /&gt;
	 */&lt;br /&gt;
	const stall = getField(&#039;stall_id&#039;);&lt;br /&gt;
	const festival = getField(&#039;festival_id&#039;);&lt;br /&gt;
	const venue = getField(&#039;venue_id&#039;);&lt;br /&gt;
	const year = getField(&#039;year&#039;);&lt;br /&gt;
&lt;br /&gt;
	if (&lt;br /&gt;
		!stall ||&lt;br /&gt;
		!festival ||&lt;br /&gt;
		!venue ||&lt;br /&gt;
		!year&lt;br /&gt;
	) {&lt;br /&gt;
		clearWarning();&lt;br /&gt;
		return;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	const stallValue = stall.value.trim();&lt;br /&gt;
	const festivalValue = festival.value.trim();&lt;br /&gt;
	const venueValue = venue.value.trim();&lt;br /&gt;
	const yearValue = year.value.trim();&lt;br /&gt;
&lt;br /&gt;
	if (&lt;br /&gt;
		!stallValue ||&lt;br /&gt;
		!festivalValue ||&lt;br /&gt;
		!/^\d{4}$/.test(yearValue)&lt;br /&gt;
	) {&lt;br /&gt;
		clearWarning();&lt;br /&gt;
		return;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	/*&lt;br /&gt;
	 * async / await は使わず、&lt;br /&gt;
	 * Promise の then() で処理する。&lt;br /&gt;
	 */&lt;br /&gt;
	Promise.all([&lt;br /&gt;
		resolveId(&lt;br /&gt;
			&#039;Stalls&#039;,&lt;br /&gt;
			&#039;stall_id&#039;,&lt;br /&gt;
			&#039;name&#039;,&lt;br /&gt;
			stallValue&lt;br /&gt;
		),&lt;br /&gt;
		resolveId(&lt;br /&gt;
			&#039;Festivals&#039;,&lt;br /&gt;
			&#039;festival_id&#039;,&lt;br /&gt;
			&#039;name&#039;,&lt;br /&gt;
			festivalValue&lt;br /&gt;
		),&lt;br /&gt;
		venueValue !== &#039;&#039;&lt;br /&gt;
			? resolveId(&lt;br /&gt;
				&#039;Venues&#039;,&lt;br /&gt;
				&#039;venue_id&#039;,&lt;br /&gt;
				&#039;_pageName&#039;,&lt;br /&gt;
				venueValue&lt;br /&gt;
			)&lt;br /&gt;
			: Promise.resolve(null)&lt;br /&gt;
	])&lt;br /&gt;
	.then(function (ids) {&lt;br /&gt;
		if (currentRequest !== requestId) {&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if (&lt;br /&gt;
			!ids[0] ||&lt;br /&gt;
			!ids[1] ||&lt;br /&gt;
			(&lt;br /&gt;
				venueValue !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
				!ids[2]&lt;br /&gt;
			)&lt;br /&gt;
		) {&lt;br /&gt;
			clearWarning();&lt;br /&gt;
			return null;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		let where =&lt;br /&gt;
			&#039;festival_id=&#039; +&lt;br /&gt;
			ids[1] +&lt;br /&gt;
			&#039; AND year=&#039; +&lt;br /&gt;
			yearValue +&lt;br /&gt;
			&#039; AND stall_id=&#039; +&lt;br /&gt;
			ids[0];&lt;br /&gt;
&lt;br /&gt;
		if (ids[2]) {&lt;br /&gt;
			where +=&lt;br /&gt;
				&#039; AND venue_id=&#039; +&lt;br /&gt;
				ids[2];&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
return cargoQuery(&lt;br /&gt;
	&#039;FestivalStallPlacements&#039;,&lt;br /&gt;
	&#039;placement_id=placement_id,&#039; +&lt;br /&gt;
		&#039;location_note=location_note,&#039; +&lt;br /&gt;
		&#039;latitude=latitude,&#039; +&lt;br /&gt;
		&#039;longitude=longitude,&#039; +&lt;br /&gt;
		&#039;position_status=position_status,&#039; +&lt;br /&gt;
		&#039;position_accuracy_m=position_accuracy_m,&#039; +&lt;br /&gt;
		&#039;status=status,&#039; +&lt;br /&gt;
		&#039;verification_status=verification_status,&#039; +&lt;br /&gt;
		&#039;last_confirmed=last_confirmed,&#039; +&lt;br /&gt;
		&#039;_pageName=page_name&#039;,&lt;br /&gt;
	where,&lt;br /&gt;
	50&lt;br /&gt;
).then(function (rows) {&lt;br /&gt;
			if (currentRequest !== requestId) {&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			console.log(&lt;br /&gt;
				&#039;FestivalStallPlacement候補:&#039;,&lt;br /&gt;
				where,&lt;br /&gt;
				rows&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
			if (rows.length === 0) {&lt;br /&gt;
				clearWarning();&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			/*&lt;br /&gt;
			 * 今回は警告のみ。&lt;br /&gt;
			 * 同条件の既存データをすべて表示する。&lt;br /&gt;
			 */&lt;br /&gt;
			const rawPageName =&lt;br /&gt;
				String(&lt;br /&gt;
					mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
					&#039;&#039;&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
			const formEditMarker =&lt;br /&gt;
				&#039;/FestivalStallPlacement/&#039;;&lt;br /&gt;
&lt;br /&gt;
			const markerIndex =&lt;br /&gt;
				rawPageName.indexOf(&lt;br /&gt;
					formEditMarker&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
			const currentPlacementPage =&lt;br /&gt;
				markerIndex &amp;gt;= 0&lt;br /&gt;
					? rawPageName&lt;br /&gt;
						.slice(&lt;br /&gt;
							markerIndex +&lt;br /&gt;
							formEditMarker.length&lt;br /&gt;
						)&lt;br /&gt;
						.replace(/_/g, &#039; &#039;)&lt;br /&gt;
						.trim()&lt;br /&gt;
					: &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
			const filteredRows =&lt;br /&gt;
				currentPlacementPage&lt;br /&gt;
					? rows.filter(function (row) {&lt;br /&gt;
						return (&lt;br /&gt;
							String(&lt;br /&gt;
								row.page_name ||&lt;br /&gt;
								&#039;&#039;&lt;br /&gt;
							)&lt;br /&gt;
								.replace(/_/g, &#039; &#039;)&lt;br /&gt;
								.trim() !==&lt;br /&gt;
							currentPlacementPage&lt;br /&gt;
						);&lt;br /&gt;
					})&lt;br /&gt;
					: rows;&lt;br /&gt;
&lt;br /&gt;
			if (filteredRows.length === 0) {&lt;br /&gt;
				clearWarning();&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			showCandidates(&lt;br /&gt;
				filteredRows,&lt;br /&gt;
				venueValue !== &#039;&#039;&lt;br /&gt;
			);&lt;br /&gt;
		});&lt;br /&gt;
	})&lt;br /&gt;
	.catch(function (error) {&lt;br /&gt;
		console.error(&lt;br /&gt;
			&#039;FestivalStallPlacement候補確認エラー:&#039;,&lt;br /&gt;
			error&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		showFailure();&lt;br /&gt;
	});&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
		function scheduleCheck() {&lt;br /&gt;
			window.clearTimeout(timer);&lt;br /&gt;
&lt;br /&gt;
			timer = window.setTimeout(&lt;br /&gt;
				checkDuplicates,&lt;br /&gt;
				300&lt;br /&gt;
			);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * form自身へイベントを設定する。&lt;br /&gt;
		 * dropdownが後から置き換わっても拾える。&lt;br /&gt;
		 */&lt;br /&gt;
		form.addEventListener(&#039;change&#039;, function (event) {&lt;br /&gt;
			const name = event.target.name || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
			if (&lt;br /&gt;
				name ===&lt;br /&gt;
					&#039;FestivalStallPlacement[stall_id]&#039; ||&lt;br /&gt;
				name ===&lt;br /&gt;
					&#039;FestivalStallPlacement[festival_id]&#039; ||&lt;br /&gt;
				name ===&lt;br /&gt;
					&#039;FestivalStallPlacement[venue_id]&#039; ||&lt;br /&gt;
				name ===&lt;br /&gt;
					&#039;FestivalStallPlacement[year]&#039;&lt;br /&gt;
			) {&lt;br /&gt;
				scheduleCheck();&lt;br /&gt;
			}&lt;br /&gt;
		});&lt;br /&gt;
&lt;br /&gt;
		form.addEventListener(&#039;input&#039;, function (event) {&lt;br /&gt;
			if (&lt;br /&gt;
				event.target.name ===&lt;br /&gt;
				&#039;FestivalStallPlacement[year]&#039;&lt;br /&gt;
			) {&lt;br /&gt;
				scheduleCheck();&lt;br /&gt;
			}&lt;br /&gt;
		});&lt;br /&gt;
&lt;br /&gt;
		scheduleCheck();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
		document.addEventListener(&lt;br /&gt;
			&#039;DOMContentLoaded&#039;,&lt;br /&gt;
			setupDuplicateWarning&lt;br /&gt;
		);&lt;br /&gt;
	} else {&lt;br /&gt;
		setupDuplicateWarning();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	mw.hook(&#039;pf.formSetupAfter&#039;).add(&lt;br /&gt;
		setupDuplicateWarning&lt;br /&gt;
	);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * FestivalStallMenuOffering&lt;br /&gt;
 * 入力検証・日本語表示&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var FORM_ID = &#039;pfForm&#039;;&lt;br /&gt;
&lt;br /&gt;
    var availabilityLabels = {&lt;br /&gt;
        available: &#039;販売中&#039;,&lt;br /&gt;
        unknown: &#039;未確認&#039;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    var verificationLabels = {&lt;br /&gt;
        verified: &#039;確認済み&#039;,&lt;br /&gt;
        partially_verified: &#039;一部確認済み&#039;,&lt;br /&gt;
        unverified: &#039;未確認&#039;,&lt;br /&gt;
        outdated: &#039;情報が古い可能性あり&#039;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function isOfferingField(element) {&lt;br /&gt;
        return !!(&lt;br /&gt;
            element &amp;amp;&amp;amp;&lt;br /&gt;
            element.name &amp;amp;&amp;amp;&lt;br /&gt;
            element.name.indexOf(&lt;br /&gt;
                &#039;FestivalStallMenuOffering[&#039;&lt;br /&gt;
            ) === 0&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isTemplateField(element) {&lt;br /&gt;
        return !!(&lt;br /&gt;
            element &amp;amp;&amp;amp;&lt;br /&gt;
            element.name &amp;amp;&amp;amp;&lt;br /&gt;
            element.name.indexOf(&#039;[num]&#039;) !== -1&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fieldNameEndsWith(element, suffix) {&lt;br /&gt;
        return !!(&lt;br /&gt;
            element &amp;amp;&amp;amp;&lt;br /&gt;
            element.name &amp;amp;&amp;amp;&lt;br /&gt;
            element.name.slice(-suffix.length) === suffix&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function localizeSelect(select, labels) {&lt;br /&gt;
        if (!select) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Array.from(select.options).forEach(&lt;br /&gt;
            function (option) {&lt;br /&gt;
                if (&lt;br /&gt;
                    Object.prototype.hasOwnProperty.call(&lt;br /&gt;
                        labels,&lt;br /&gt;
                        option.value&lt;br /&gt;
                    ) &amp;amp;&amp;amp;&lt;br /&gt;
                    option.textContent !==&lt;br /&gt;
                        labels[option.value]&lt;br /&gt;
                ) {&lt;br /&gt;
                    option.textContent =&lt;br /&gt;
                        labels[option.value];&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validatePrice(input) {&lt;br /&gt;
        var value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !/^\d+$/.test(value)&lt;br /&gt;
        ) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;価格は0以上の整数で入力してください（例：600）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateServingQuantity(input) {&lt;br /&gt;
        var value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !/^(?:\d+(?:\.\d+)?|\.\d+)$/.test(value)&lt;br /&gt;
        ) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;提供数量は0以上の数値で入力してください（例：8、1、0.5）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateLimitedQuantity(input) {&lt;br /&gt;
        var value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !/^\d+$/.test(value)&lt;br /&gt;
        ) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;限定数量は0以上の整数で入力してください（例：100）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateSortOrder(input) {&lt;br /&gt;
        var value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !/^\d+$/.test(value)&lt;br /&gt;
        ) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;表示順は0以上の整数で入力してください（例：1）&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateSourceUrl(input) {&lt;br /&gt;
        var value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            var url = new URL(value);&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                url.protocol !== &#039;http:&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                url.protocol !== &#039;https:&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    &#039;情報元URLは http:// または https:// で始まるURLを入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        } catch (e) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;情報元URLを正しいURL形式で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateLastConfirmed(input) {&lt;br /&gt;
        var value = input.value;&lt;br /&gt;
        var max = input.max;&lt;br /&gt;
&lt;br /&gt;
        input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            max !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            value &amp;gt; max&lt;br /&gt;
        ) {&lt;br /&gt;
            input.setCustomValidity(&lt;br /&gt;
                &#039;未来の日付は入力できません。&#039; +&lt;br /&gt;
                max.replace(/-/g, &#039;/&#039;) +&lt;br /&gt;
                &#039;以前の日付を入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getVisibleDateInput(dateInput) {&lt;br /&gt;
        var widget =&lt;br /&gt;
            dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!widget) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return widget.querySelector(&lt;br /&gt;
            &#039;input[type=&amp;quot;text&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDateErrorElement(dateInput) {&lt;br /&gt;
        var widget =&lt;br /&gt;
            dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!widget) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var next =&lt;br /&gt;
            widget.nextElementSibling;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            next &amp;amp;&amp;amp;&lt;br /&gt;
            next.classList.contains(&lt;br /&gt;
                &#039;stall-offering-last-confirmed-error&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var error =&lt;br /&gt;
            document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 既存の最終確認日エラー用CSSも利用する。&lt;br /&gt;
         */&lt;br /&gt;
        error.className =&lt;br /&gt;
            &#039;stall-last-confirmed-error &#039; +&lt;br /&gt;
            &#039;stall-offering-last-confirmed-error&#039;;&lt;br /&gt;
&lt;br /&gt;
        error.setAttribute(&lt;br /&gt;
            &#039;role&#039;,&lt;br /&gt;
            &#039;alert&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        error.hidden = true;&lt;br /&gt;
&lt;br /&gt;
        widget.insertAdjacentElement(&lt;br /&gt;
            &#039;afterend&#039;,&lt;br /&gt;
            error&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return error;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showDateError(dateInput) {&lt;br /&gt;
        var visibleInput =&lt;br /&gt;
            getVisibleDateInput(dateInput);&lt;br /&gt;
&lt;br /&gt;
        var error =&lt;br /&gt;
            getDateErrorElement(dateInput);&lt;br /&gt;
&lt;br /&gt;
        if (!error) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var maxDate =&lt;br /&gt;
            dateInput.max&lt;br /&gt;
                ? dateInput.max.replace(/-/g, &#039;/&#039;)&lt;br /&gt;
                : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            dateInput.validity.rangeOverflow ||&lt;br /&gt;
            (&lt;br /&gt;
                dateInput.value &amp;amp;&amp;amp;&lt;br /&gt;
                dateInput.max &amp;amp;&amp;amp;&lt;br /&gt;
                dateInput.value &amp;gt; dateInput.max&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            error.textContent =&lt;br /&gt;
                &#039;未来の日付は入力できません。&#039; +&lt;br /&gt;
                maxDate +&lt;br /&gt;
                &#039;以前の日付を入力してください。&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            error.textContent =&lt;br /&gt;
                dateInput.validationMessage ||&lt;br /&gt;
                &#039;正しい日付を入力してください。&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        error.hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (visibleInput) {&lt;br /&gt;
            visibleInput.setAttribute(&lt;br /&gt;
                &#039;aria-invalid&#039;,&lt;br /&gt;
                &#039;true&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearDateError(dateInput) {&lt;br /&gt;
        var visibleInput =&lt;br /&gt;
            getVisibleDateInput(dateInput);&lt;br /&gt;
&lt;br /&gt;
        var widget =&lt;br /&gt;
            dateInput.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
        var error = null;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            widget &amp;amp;&amp;amp;&lt;br /&gt;
            widget.nextElementSibling &amp;amp;&amp;amp;&lt;br /&gt;
            widget.nextElementSibling.classList.contains(&lt;br /&gt;
                &#039;stall-offering-last-confirmed-error&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            error =&lt;br /&gt;
                widget.nextElementSibling;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (error) {&lt;br /&gt;
            error.hidden = true;&lt;br /&gt;
            error.textContent = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (visibleInput) {&lt;br /&gt;
            visibleInput.removeAttribute(&lt;br /&gt;
                &#039;aria-invalid&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getLimitedQuantityInput(&lt;br /&gt;
        checkbox,&lt;br /&gt;
        form&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!checkbox || !checkbox.name) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var quantityName =&lt;br /&gt;
            checkbox.name.replace(&lt;br /&gt;
                /\[limited\]\[value\]$/,&lt;br /&gt;
                &#039;[limited_quantity]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        return Array.from(&lt;br /&gt;
            form.querySelectorAll(&lt;br /&gt;
                &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ).find(&lt;br /&gt;
            function (input) {&lt;br /&gt;
                return input.name === quantityName;&lt;br /&gt;
            }&lt;br /&gt;
        ) || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateLimitedState(&lt;br /&gt;
        checkbox,&lt;br /&gt;
        form,&lt;br /&gt;
        clearWhenOff&lt;br /&gt;
    ) {&lt;br /&gt;
        var quantityInput =&lt;br /&gt;
            getLimitedQuantityInput(&lt;br /&gt;
                checkbox,&lt;br /&gt;
                form&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!quantityInput) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (checkbox.checked) {&lt;br /&gt;
            quantityInput.disabled = false;&lt;br /&gt;
            quantityInput.removeAttribute(&lt;br /&gt;
                &#039;aria-disabled&#039;&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            if (clearWhenOff) {&lt;br /&gt;
                quantityInput.value = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            quantityInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
            quantityInput.disabled = true;&lt;br /&gt;
            quantityInput.setAttribute(&lt;br /&gt;
                &#039;aria-disabled&#039;,&lt;br /&gt;
                &#039;true&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateField(element) {&lt;br /&gt;
        if (&lt;br /&gt;
            !isOfferingField(element) ||&lt;br /&gt;
            isTemplateField(element)&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[price]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validatePrice(element);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[serving_quantity]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateServingQuantity(element);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[limited_quantity]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateLimitedQuantity(element);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[sort_order]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateSortOrder(element);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[source_url]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateSourceUrl(element);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            fieldNameEndsWith(&lt;br /&gt;
                element,&lt;br /&gt;
                &#039;[last_confirmed]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateLastConfirmed(element);&lt;br /&gt;
&lt;br /&gt;
            if (element.validity.valid) {&lt;br /&gt;
                clearDateError(element);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initializeFields(form) {&lt;br /&gt;
        /*&lt;br /&gt;
         * 販売状態を日本語化。&lt;br /&gt;
         * [num]も変更しておくことで、&lt;br /&gt;
         * 後から追加されるmultipleにも反映される。&lt;br /&gt;
         */&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;select[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[availability]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (select) {&lt;br /&gt;
                localizeSelect(&lt;br /&gt;
                    select,&lt;br /&gt;
                    availabilityLabels&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 確認状態を日本語化。&lt;br /&gt;
         */&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;select[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[verification_status]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (select) {&lt;br /&gt;
                localizeSelect(&lt;br /&gt;
                    select,&lt;br /&gt;
                    verificationLabels&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 数値入力向けキーボード。&lt;br /&gt;
         */&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[price]&amp;quot;],&#039; +&lt;br /&gt;
            &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[limited_quantity]&amp;quot;],&#039; +&lt;br /&gt;
            &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[sort_order]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (input) {&lt;br /&gt;
                input.inputMode = &#039;numeric&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[serving_quantity]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (input) {&lt;br /&gt;
                input.inputMode = &#039;decimal&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;input[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[source_url]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (input) {&lt;br /&gt;
                input.inputMode = &#039;url&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 限定数量欄のON/OFF。&lt;br /&gt;
         */&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;input[type=&amp;quot;checkbox&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
            &#039;[name$=&amp;quot;[limited][value]&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (checkbox) {&lt;br /&gt;
                updateLimitedState(&lt;br /&gt;
                    checkbox,&lt;br /&gt;
                    form,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 現在値を一度検証。&lt;br /&gt;
         * [num]は除外。&lt;br /&gt;
         */&lt;br /&gt;
        form.querySelectorAll(&lt;br /&gt;
            &#039;[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039;&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (element) {&lt;br /&gt;
                validateField(element);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setupOfferingValidation() {&lt;br /&gt;
        var form =&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                FORM_ID&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!form) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * wikipage.content 等で再度呼ばれても&lt;br /&gt;
         * イベントを二重登録しない。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            form.dataset&lt;br /&gt;
                .offeringValidationInitialized ===&lt;br /&gt;
            &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            initializeFields(form);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        form.dataset&lt;br /&gt;
            .offeringValidationInitialized =&lt;br /&gt;
            &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * multipleで後から追加された項目にも効くよう&lt;br /&gt;
         * form側でイベント委譲。&lt;br /&gt;
         */&lt;br /&gt;
        form.addEventListener(&lt;br /&gt;
            &#039;input&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                validateField(&lt;br /&gt;
                    event.target&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        form.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                var target =&lt;br /&gt;
                    event.target;&lt;br /&gt;
&lt;br /&gt;
                if (!isOfferingField(target)) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    target.type === &#039;checkbox&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    fieldNameEndsWith(&lt;br /&gt;
                        target,&lt;br /&gt;
                        &#039;[limited][value]&#039;&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    updateLimitedState(&lt;br /&gt;
                        target,&lt;br /&gt;
                        form,&lt;br /&gt;
                        true&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                validateField(target);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * Page Forms のカレンダー選択では、&lt;br /&gt;
 * visible input に blur が発生する場合がある。&lt;br /&gt;
 * 対応する非表示 date input を取得して再検証する。&lt;br /&gt;
 */&lt;br /&gt;
form.addEventListener(&lt;br /&gt;
    &#039;blur&#039;,&lt;br /&gt;
    function (event) {&lt;br /&gt;
var target = event.target;&lt;br /&gt;
&lt;br /&gt;
if (&lt;br /&gt;
    !target ||&lt;br /&gt;
    typeof target.closest !== &#039;function&#039;&lt;br /&gt;
) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var widget =&lt;br /&gt;
    target.closest(&#039;.oo-ui-widget&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!widget) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var dateInput =&lt;br /&gt;
            widget.querySelector(&lt;br /&gt;
                &#039;input[type=&amp;quot;date&amp;quot;]&#039; +&lt;br /&gt;
                &#039;[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
                &#039;[name$=&amp;quot;[last_confirmed]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !dateInput ||&lt;br /&gt;
            isTemplateField(dateInput)&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(&lt;br /&gt;
            function () {&lt;br /&gt;
                validateField(dateInput);&lt;br /&gt;
&lt;br /&gt;
                if (dateInput.validity.valid) {&lt;br /&gt;
                    clearDateError(dateInput);&lt;br /&gt;
                } else {&lt;br /&gt;
                    showDateError(dateInput);&lt;br /&gt;
                }&lt;br /&gt;
            },&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
    },&lt;br /&gt;
    true&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * invalidイベントは通常bubbleしないため&lt;br /&gt;
         * capture=trueで取得する。&lt;br /&gt;
         */&lt;br /&gt;
        form.addEventListener(&lt;br /&gt;
            &#039;invalid&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                var target =&lt;br /&gt;
                    event.target;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !isOfferingField(target) ||&lt;br /&gt;
                    isTemplateField(target)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                validateField(target);&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    fieldNameEndsWith(&lt;br /&gt;
                        target,&lt;br /&gt;
                        &#039;[last_confirmed]&#039;&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
&lt;br /&gt;
                    showDateError(target);&lt;br /&gt;
&lt;br /&gt;
                    var visibleInput =&lt;br /&gt;
                        getVisibleDateInput(&lt;br /&gt;
                            target&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (visibleInput) {&lt;br /&gt;
                        window.setTimeout(&lt;br /&gt;
                            function () {&lt;br /&gt;
                                visibleInput.focus();&lt;br /&gt;
                            },&lt;br /&gt;
                            0&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            },&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 「販売商品を追加」でDOMが増えた場合の初期化。&lt;br /&gt;
         */&lt;br /&gt;
        var mutationTimer = null;&lt;br /&gt;
&lt;br /&gt;
        var observer =&lt;br /&gt;
            new MutationObserver(&lt;br /&gt;
                function () {&lt;br /&gt;
                    window.clearTimeout(&lt;br /&gt;
                        mutationTimer&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    mutationTimer =&lt;br /&gt;
                        window.setTimeout(&lt;br /&gt;
                            function () {&lt;br /&gt;
                                initializeFields(&lt;br /&gt;
                                    form&lt;br /&gt;
                                );&lt;br /&gt;
                            },&lt;br /&gt;
                            100&lt;br /&gt;
                        );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        observer.observe(&lt;br /&gt;
            form,&lt;br /&gt;
            {&lt;br /&gt;
                childList: true,&lt;br /&gt;
                subtree: true&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        initializeFields(form);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState ===&lt;br /&gt;
        &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            setupOfferingValidation&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        setupOfferingValidation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&lt;br /&gt;
        &#039;wikipage.content&#039;&lt;br /&gt;
    ).add(&lt;br /&gt;
        setupOfferingValidation&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&lt;br /&gt;
        &#039;pf.formSetupAfter&#039;&lt;br /&gt;
    ).add(&lt;br /&gt;
        setupOfferingValidation&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
mw.loader.using(&#039;mediawiki.api&#039;).then(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.__festivalStallMenuFilterInitialized) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.__festivalStallMenuFilterInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    const STALL_SELECTOR =&lt;br /&gt;
        &#039;select[name=&amp;quot;FestivalStallPlacement[stall_id]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const MENU_SELECTOR =&lt;br /&gt;
        &#039;select[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;][name$=&amp;quot;[menu_item_id]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const TEMPLATE_MENU_SELECTOR =&lt;br /&gt;
        &#039;select[name=&amp;quot;FestivalStallMenuOffering[num][menu_item_id]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    let requestSerial = 0;&lt;br /&gt;
    let observerTimer = null;&lt;br /&gt;
    let applying = false;&lt;br /&gt;
&lt;br /&gt;
    const menuCache = {};&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * FestivalStallPlacement フォーム以外では&lt;br /&gt;
 * この連動機能を起動しない。&lt;br /&gt;
 */&lt;br /&gt;
const stallSelect =&lt;br /&gt;
    document.querySelector(STALL_SELECTOR);&lt;br /&gt;
&lt;br /&gt;
if (!stallSelect) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * Page Formsの雛形が持つ全商品optionを最初に保存&lt;br /&gt;
 */&lt;br /&gt;
const templateSelect =&lt;br /&gt;
    document.querySelector(TEMPLATE_MENU_SELECTOR);&lt;br /&gt;
&lt;br /&gt;
if (!templateSelect) {&lt;br /&gt;
    console.error(&lt;br /&gt;
        &#039;販売商品の雛形SELECTが見つかりません。&#039;&lt;br /&gt;
    );&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
    const masterOptions =&lt;br /&gt;
        [...templateSelect.options].map(&lt;br /&gt;
            function (option) {&lt;br /&gt;
                return option.cloneNode(true);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    function cargoQuote(value) {&lt;br /&gt;
        return &amp;quot;&#039;&amp;quot; + String(value)&lt;br /&gt;
            .replace(/\\/g, &#039;\\\\&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &amp;quot;\\&#039;&amp;quot;) + &amp;quot;&#039;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cargoRows(res) {&lt;br /&gt;
        return (res.cargoquery || []).map(&lt;br /&gt;
            function (row) {&lt;br /&gt;
                return row.title || {};&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getRealMenuSelects() {&lt;br /&gt;
        return [&lt;br /&gt;
            ...document.querySelectorAll(&lt;br /&gt;
                MENU_SELECTOR&lt;br /&gt;
            )&lt;br /&gt;
        ].filter(function (select) {&lt;br /&gt;
            return !select.name.includes(&#039;[num]&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveStallId(stallName) {&lt;br /&gt;
&lt;br /&gt;
        return api.get({&lt;br /&gt;
            action: &#039;cargoquery&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            tables: &#039;Stalls&#039;,&lt;br /&gt;
            fields:&lt;br /&gt;
                &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
                &#039;name=name&#039;,&lt;br /&gt;
            where:&lt;br /&gt;
                &#039;name=&#039; +&lt;br /&gt;
                cargoQuote(stallName),&lt;br /&gt;
            limit: 20&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
&lt;br /&gt;
            const rows =&lt;br /&gt;
                cargoRows(res);&lt;br /&gt;
&lt;br /&gt;
            if (rows.length === 1) {&lt;br /&gt;
                return rows[0].stall_id;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 同名表示が&lt;br /&gt;
             * 名前 (ID)&lt;br /&gt;
             * になっている場合&lt;br /&gt;
             */&lt;br /&gt;
            const match =&lt;br /&gt;
                String(stallName)&lt;br /&gt;
                    .match(/\((\d+)\)$/);&lt;br /&gt;
&lt;br /&gt;
            if (!match) {&lt;br /&gt;
                throw new Error(&lt;br /&gt;
                    &#039;屋台を1件に特定できません: &#039; +&lt;br /&gt;
                    stallName&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return match[1];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadMenus(stallId) {&lt;br /&gt;
&lt;br /&gt;
        const key =&lt;br /&gt;
            String(stallId);&lt;br /&gt;
&lt;br /&gt;
        if (menuCache[key]) {&lt;br /&gt;
            return Promise.resolve(&lt;br /&gt;
                menuCache[key]&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return api.get({&lt;br /&gt;
            action: &#039;cargoquery&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            tables: &#039;StallMenuItems&#039;,&lt;br /&gt;
fields:&lt;br /&gt;
    &#039;menu_item_id=menu_item_id,&#039; +&lt;br /&gt;
    &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
    &#039;name=name,&#039; +&lt;br /&gt;
    &#039;status=status&#039;,&lt;br /&gt;
            where:&lt;br /&gt;
                &#039;stall_id=&#039; +&lt;br /&gt;
                Number(stallId) +&lt;br /&gt;
                &amp;quot; AND status=&#039;active&#039;&amp;quot;,&lt;br /&gt;
            order_by:&lt;br /&gt;
    &#039;menu_item_id&#039;,&lt;br /&gt;
            limit: 100&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
&lt;br /&gt;
            const rows =&lt;br /&gt;
                cargoRows(res);&lt;br /&gt;
&lt;br /&gt;
            menuCache[key] =&lt;br /&gt;
                rows;&lt;br /&gt;
&lt;br /&gt;
            return rows;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function optionBelongsToMenu(&lt;br /&gt;
        option,&lt;br /&gt;
        menu&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const name =&lt;br /&gt;
            String(menu.name || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        const id =&lt;br /&gt;
            String(&lt;br /&gt;
                menu.menu_item_id || &#039;&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const value =&lt;br /&gt;
            String(option.value || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        const text =&lt;br /&gt;
            String(&lt;br /&gt;
                option.textContent || &#039;&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 商品名が一意&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            value === name ||&lt;br /&gt;
            text === name&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Page Formsによる&lt;br /&gt;
         * 同名商品の識別表示&lt;br /&gt;
         *&lt;br /&gt;
         * たこ焼き (1)&lt;br /&gt;
         * たこ焼き (3)&lt;br /&gt;
         */&lt;br /&gt;
        const mapped =&lt;br /&gt;
            name + &#039; (&#039; + id + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
        return (&lt;br /&gt;
            value === mapped ||&lt;br /&gt;
            text === mapped&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeOptions(menus) {&lt;br /&gt;
&lt;br /&gt;
        const options = [];&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 空欄&lt;br /&gt;
         */&lt;br /&gt;
        const blank =&lt;br /&gt;
            masterOptions.find(&lt;br /&gt;
                function (option) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        option.value === &#039;&#039;&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (blank) {&lt;br /&gt;
            options.push(&lt;br /&gt;
                blank.cloneNode(true)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            options.push(&lt;br /&gt;
                new Option(&#039;&#039;, &#039;&#039;)&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        menus.forEach(&lt;br /&gt;
            function (menu) {&lt;br /&gt;
&lt;br /&gt;
                const option =&lt;br /&gt;
                    masterOptions.find(&lt;br /&gt;
                        function (candidate) {&lt;br /&gt;
                            return optionBelongsToMenu(&lt;br /&gt;
                                candidate,&lt;br /&gt;
                                menu&lt;br /&gt;
                            );&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (option) {&lt;br /&gt;
                    options.push(&lt;br /&gt;
                        option.cloneNode(true)&lt;br /&gt;
                    );&lt;br /&gt;
                } else {&lt;br /&gt;
                    console.warn(&lt;br /&gt;
                        &#039;Page Formsのoptionを特定できません:&#039;,&lt;br /&gt;
                        menu&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return options;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function optionSignature(select) {&lt;br /&gt;
&lt;br /&gt;
        return [...select.options]&lt;br /&gt;
            .map(function (option) {&lt;br /&gt;
                return (&lt;br /&gt;
                    option.value +&lt;br /&gt;
                    &#039;::&#039; +&lt;br /&gt;
                    option.textContent&lt;br /&gt;
                );&lt;br /&gt;
            })&lt;br /&gt;
            .join(&#039;||&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function filterMenuSelects(&lt;br /&gt;
        menus,&lt;br /&gt;
        clearSelection&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const desiredTemplate =&lt;br /&gt;
            makeOptions(menus);&lt;br /&gt;
&lt;br /&gt;
        const desiredSignature =&lt;br /&gt;
            desiredTemplate&lt;br /&gt;
                .map(function (option) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        option.value +&lt;br /&gt;
                        &#039;::&#039; +&lt;br /&gt;
                        option.textContent&lt;br /&gt;
                    );&lt;br /&gt;
                })&lt;br /&gt;
                .join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
        applying = true;&lt;br /&gt;
&lt;br /&gt;
        getRealMenuSelects().forEach(&lt;br /&gt;
            function (select) {&lt;br /&gt;
&lt;br /&gt;
                const previousValue =&lt;br /&gt;
                    select.value;&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * すでに正しい候補なら&lt;br /&gt;
                 * DOMを触らない&lt;br /&gt;
                 */&lt;br /&gt;
                if (&lt;br /&gt;
                    optionSignature(select) ===&lt;br /&gt;
                    desiredSignature&lt;br /&gt;
                ) {&lt;br /&gt;
                    if (clearSelection &amp;amp;&amp;amp;&lt;br /&gt;
                        select.value !== &#039;&#039;) {&lt;br /&gt;
&lt;br /&gt;
                        select.value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                        if (window.jQuery) {&lt;br /&gt;
                            jQuery(select)&lt;br /&gt;
                                .trigger(&#039;change&#039;);&lt;br /&gt;
                        }&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const newOptions =&lt;br /&gt;
                    desiredTemplate.map(&lt;br /&gt;
                        function (option) {&lt;br /&gt;
                            return option&lt;br /&gt;
                                .cloneNode(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                select.replaceChildren(&lt;br /&gt;
                    ...newOptions&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (!clearSelection) {&lt;br /&gt;
&lt;br /&gt;
                    const exists =&lt;br /&gt;
                        [...select.options]&lt;br /&gt;
                            .some(&lt;br /&gt;
                                function (option) {&lt;br /&gt;
                                    return (&lt;br /&gt;
                                        option.value ===&lt;br /&gt;
                                        previousValue&lt;br /&gt;
                                    );&lt;br /&gt;
                                }&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                    if (exists) {&lt;br /&gt;
                        select.value =&lt;br /&gt;
                            previousValue;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (clearSelection) {&lt;br /&gt;
                    select.value = &#039;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.jQuery) {&lt;br /&gt;
                    jQuery(select)&lt;br /&gt;
                        .trigger(&#039;change&#039;);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MutationObserverに&lt;br /&gt;
         * 自分自身の変更を拾わせない&lt;br /&gt;
         */&lt;br /&gt;
        setTimeout(&lt;br /&gt;
            function () {&lt;br /&gt;
                applying = false;&lt;br /&gt;
            },&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function refreshMenus(&lt;br /&gt;
        clearSelection&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const stall =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                STALL_SELECTOR&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
if (!stall) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!stall.value) {&lt;br /&gt;
    /*&lt;br /&gt;
     * 屋台が未選択なら、&lt;br /&gt;
     * 進行中の古い非同期処理を無効化し、&lt;br /&gt;
     * 商品候補を空欄だけに戻す。&lt;br /&gt;
     */&lt;br /&gt;
    ++requestSerial;&lt;br /&gt;
&lt;br /&gt;
    filterMenuSelects(&lt;br /&gt;
        [],&lt;br /&gt;
        true&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
        const serial =&lt;br /&gt;
            ++requestSerial;&lt;br /&gt;
&lt;br /&gt;
        const stallName =&lt;br /&gt;
            stall.value;&lt;br /&gt;
&lt;br /&gt;
        resolveStallId(&lt;br /&gt;
            stallName&lt;br /&gt;
        )&lt;br /&gt;
        .then(function (stallId) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                serial !==&lt;br /&gt;
                requestSerial&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            console.log(&lt;br /&gt;
                &#039;[屋台→商品V2]&#039;,&lt;br /&gt;
                stallName,&lt;br /&gt;
                &#039;→ stall_id=&#039; +&lt;br /&gt;
                stallId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return loadMenus(&lt;br /&gt;
                stallId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        })&lt;br /&gt;
        .then(function (menus) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !menus ||&lt;br /&gt;
                serial !==&lt;br /&gt;
                    requestSerial&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            console.log(&lt;br /&gt;
                &#039;[販売商品候補V2]&#039;,&lt;br /&gt;
                menus&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            filterMenuSelects(&lt;br /&gt;
                menus,&lt;br /&gt;
                clearSelection&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function (err) {&lt;br /&gt;
&lt;br /&gt;
            console.error(&lt;br /&gt;
                &#039;[屋台→商品V2] エラー:&#039;,&lt;br /&gt;
                err&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Page Formsによる&lt;br /&gt;
     * option再生成を検出&lt;br /&gt;
     */&lt;br /&gt;
    function mutationTouchesMenus(&lt;br /&gt;
        mutation&lt;br /&gt;
    ) {&lt;br /&gt;
&lt;br /&gt;
        const target =&lt;br /&gt;
            mutation.target;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            target.matches &amp;amp;&amp;amp;&lt;br /&gt;
            target.matches(MENU_SELECTOR)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (&lt;br /&gt;
            const node of&lt;br /&gt;
            mutation.addedNodes&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                node.nodeType !== 1&lt;br /&gt;
            ) {&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                node.matches &amp;amp;&amp;amp;&lt;br /&gt;
                node.matches(MENU_SELECTOR)&lt;br /&gt;
            ) {&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                node.querySelector &amp;amp;&amp;amp;&lt;br /&gt;
                node.querySelector(&lt;br /&gt;
                    MENU_SELECTOR&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * SELECTの中にOPTIONが追加された&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                node.tagName === &#039;OPTION&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                node.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
                node.parentElement.matches &amp;amp;&amp;amp;&lt;br /&gt;
                node.parentElement.matches(&lt;br /&gt;
                    MENU_SELECTOR&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const observer =&lt;br /&gt;
        new MutationObserver(&lt;br /&gt;
            function (mutations) {&lt;br /&gt;
&lt;br /&gt;
                if (applying) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const touched =&lt;br /&gt;
                    mutations.some(&lt;br /&gt;
                        mutationTouchesMenus&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (!touched) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                clearTimeout(&lt;br /&gt;
                    observerTimer&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                /*&lt;br /&gt;
                 * Page Formsの再初期化が&lt;br /&gt;
                 * 完了してから実行&lt;br /&gt;
                 */&lt;br /&gt;
                observerTimer =&lt;br /&gt;
                    setTimeout(&lt;br /&gt;
                        function () {&lt;br /&gt;
                            refreshMenus(false);&lt;br /&gt;
                        },&lt;br /&gt;
                        250&lt;br /&gt;
                    );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    const form =&lt;br /&gt;
        document.getElementById(&lt;br /&gt;
            &#039;pfForm&#039;&lt;br /&gt;
        ) || document.body;&lt;br /&gt;
&lt;br /&gt;
    observer.observe(&lt;br /&gt;
        form,&lt;br /&gt;
        {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 屋台変更&lt;br /&gt;
     */&lt;br /&gt;
    const stall =&lt;br /&gt;
        document.querySelector(&lt;br /&gt;
            STALL_SELECTOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
    function onStallChange() {&lt;br /&gt;
        refreshMenus(true);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
        stall.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        onStallChange&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 初期表示&lt;br /&gt;
     */&lt;br /&gt;
    refreshMenus(false);&lt;br /&gt;
&lt;br /&gt;
        console.log(&lt;br /&gt;
        &#039;屋台→販売商品連動を初期化しました。&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * FestivalStallMenuOffering&lt;br /&gt;
 * 同一Placement内の商品重複警告&lt;br /&gt;
 *&lt;br /&gt;
 * 保存は禁止しない。&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    const MENU_SELECTOR =&lt;br /&gt;
        &#039;select[name^=&amp;quot;FestivalStallMenuOffering[&amp;quot;]&#039; +&lt;br /&gt;
        &#039;[name$=&amp;quot;[menu_item_id]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    function setupOfferingDuplicateWarning() {&lt;br /&gt;
        const form =&lt;br /&gt;
            document.getElementById(&#039;pfForm&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!form) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * FestivalStallPlacementフォームだけを対象にする。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !form.querySelector(&lt;br /&gt;
                &#039;[name=&amp;quot;FestivalStallPlacement[stall_id]&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getMenuSelects() {&lt;br /&gt;
            return [&lt;br /&gt;
                ...form.querySelectorAll(&lt;br /&gt;
                    MENU_SELECTOR&lt;br /&gt;
                )&lt;br /&gt;
            ].filter(function (select) {&lt;br /&gt;
                return !select.name.includes(&#039;[num]&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getWarning() {&lt;br /&gt;
            let warning =&lt;br /&gt;
                form.querySelector(&lt;br /&gt;
                    &#039;.stall-offering-duplicate-warning&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (warning) {&lt;br /&gt;
                return warning;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const firstSelect =&lt;br /&gt;
                getMenuSelects()[0];&lt;br /&gt;
&lt;br /&gt;
            if (!firstSelect) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            warning =&lt;br /&gt;
                document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
            warning.className =&lt;br /&gt;
                &#039;stall-offering-duplicate-warning&#039;;&lt;br /&gt;
&lt;br /&gt;
            warning.setAttribute(&lt;br /&gt;
                &#039;role&#039;,&lt;br /&gt;
                &#039;status&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            warning.hidden = true;&lt;br /&gt;
&lt;br /&gt;
            warning.style.marginTop = &#039;8px&#039;;&lt;br /&gt;
            warning.style.padding = &#039;10px&#039;;&lt;br /&gt;
            warning.style.border =&lt;br /&gt;
                &#039;1px solid #a2a9b1&#039;;&lt;br /&gt;
            warning.style.borderRadius = &#039;4px&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * 警告は個別の商品行ではなく、&lt;br /&gt;
 * 販売商品multiple全体の上部に表示する。&lt;br /&gt;
 */&lt;br /&gt;
const wrapper =&lt;br /&gt;
    firstSelect.closest(&lt;br /&gt;
        &#039;.multipleTemplateWrapper&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
const list =&lt;br /&gt;
    wrapper&lt;br /&gt;
        ? wrapper.querySelector(&lt;br /&gt;
            &#039;.multipleTemplateList&#039;&lt;br /&gt;
        )&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
if (list) {&lt;br /&gt;
    list.insertAdjacentElement(&lt;br /&gt;
        &#039;beforebegin&#039;,&lt;br /&gt;
        warning&lt;br /&gt;
    );&lt;br /&gt;
} else {&lt;br /&gt;
    const container =&lt;br /&gt;
        firstSelect.closest(&#039;fieldset&#039;) ||&lt;br /&gt;
        firstSelect.closest(&#039;td&#039;) ||&lt;br /&gt;
        firstSelect.parentNode;&lt;br /&gt;
&lt;br /&gt;
    container.insertBefore(&lt;br /&gt;
        warning,&lt;br /&gt;
        container.firstChild&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
            return warning;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function clearWarning() {&lt;br /&gt;
            const warning =&lt;br /&gt;
                form.querySelector(&lt;br /&gt;
                    &#039;.stall-offering-duplicate-warning&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (!warning) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            warning.hidden = true;&lt;br /&gt;
            warning.textContent = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function checkDuplicates() {&lt;br /&gt;
            const selects =&lt;br /&gt;
                getMenuSelects();&lt;br /&gt;
&lt;br /&gt;
            const counts = {};&lt;br /&gt;
&lt;br /&gt;
            selects.forEach(function (select) {&lt;br /&gt;
                const value =&lt;br /&gt;
                    String(&lt;br /&gt;
                        select.value || &#039;&#039;&lt;br /&gt;
                    ).trim();&lt;br /&gt;
&lt;br /&gt;
                if (!value) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                counts[value] =&lt;br /&gt;
                    (counts[value] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            const duplicates =&lt;br /&gt;
                Object.keys(counts).filter(&lt;br /&gt;
                    function (value) {&lt;br /&gt;
                        return counts[value] &amp;gt; 1;&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (duplicates.length === 0) {&lt;br /&gt;
                clearWarning();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const warning =&lt;br /&gt;
                getWarning();&lt;br /&gt;
&lt;br /&gt;
            if (!warning) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            warning.textContent = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            const title =&lt;br /&gt;
                document.createElement(&#039;strong&#039;);&lt;br /&gt;
&lt;br /&gt;
            title.textContent =&lt;br /&gt;
                &#039;同じ販売商品が複数回選択されています。&#039;;&lt;br /&gt;
&lt;br /&gt;
            warning.appendChild(title);&lt;br /&gt;
&lt;br /&gt;
            const detail =&lt;br /&gt;
                document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
            detail.textContent =&lt;br /&gt;
                duplicates.join(&#039;、&#039;) +&lt;br /&gt;
                &#039; が重複しています。&#039; +&lt;br /&gt;
                &#039;重複登録でないか確認してください。&#039; +&lt;br /&gt;
                &#039;保存自体は禁止しません。&#039;;&lt;br /&gt;
&lt;br /&gt;
            warning.appendChild(detail);&lt;br /&gt;
&lt;br /&gt;
            warning.hidden = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * multipleで後から追加された行にも対応。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            form.dataset&lt;br /&gt;
                .offeringDuplicateWarning !== &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            form.dataset&lt;br /&gt;
                .offeringDuplicateWarning = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * Page Forms / Select2 は&lt;br /&gt;
 * jQueryのchangeを使う場合があるため、&lt;br /&gt;
 * jQuery側でイベント委譲する。&lt;br /&gt;
 */&lt;br /&gt;
if (window.jQuery) {&lt;br /&gt;
    jQuery(form).on(&lt;br /&gt;
        &#039;change.offeringDuplicateWarning&#039;,&lt;br /&gt;
        MENU_SELECTOR,&lt;br /&gt;
        function () {&lt;br /&gt;
            window.setTimeout(&lt;br /&gt;
                checkDuplicates,&lt;br /&gt;
                0&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
} else {&lt;br /&gt;
    /*&lt;br /&gt;
     * jQueryが無い場合のフォールバック。&lt;br /&gt;
     */&lt;br /&gt;
    form.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event.target.matches &amp;amp;&amp;amp;&lt;br /&gt;
                event.target.matches(&lt;br /&gt;
                    MENU_SELECTOR&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                window.setTimeout(&lt;br /&gt;
                    checkDuplicates,&lt;br /&gt;
                    0&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
            const observer =&lt;br /&gt;
                new MutationObserver(&lt;br /&gt;
                    function () {&lt;br /&gt;
                        window.setTimeout(&lt;br /&gt;
                            checkDuplicates,&lt;br /&gt;
                            0&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            observer.observe(&lt;br /&gt;
                form,&lt;br /&gt;
                {&lt;br /&gt;
                    childList: true,&lt;br /&gt;
                    subtree: true&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        checkDuplicates();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState === &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            setupOfferingDuplicateWarning&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        setupOfferingDuplicateWarning();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&lt;br /&gt;
        &#039;wikipage.content&#039;&lt;br /&gt;
    ).add(&lt;br /&gt;
        setupOfferingDuplicateWarning&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * StallMenuItem&lt;br /&gt;
 * 入力検証・状態日本語化&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function setupStallMenuItemValidation() {&lt;br /&gt;
        const form = document.getElementById(&#039;pfForm&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!form) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * StallMenuItemフォーム以外では何もしない。&lt;br /&gt;
         */&lt;br /&gt;
        const nameInput = form.querySelector(&lt;br /&gt;
            &#039;input[name=&amp;quot;StallMenuItem[name]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!nameInput) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 二重初期化防止&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            form.dataset.stallMenuItemValidationInitialized === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        form.dataset.stallMenuItemValidationInitialized = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * =====================================&lt;br /&gt;
         * 状態を日本語表示&lt;br /&gt;
         * =====================================&lt;br /&gt;
         */&lt;br /&gt;
        const statusLabels = {&lt;br /&gt;
            active: &#039;取扱中&#039;,&lt;br /&gt;
            inactive: &#039;一時停止&#039;,&lt;br /&gt;
            discontinued: &#039;取扱終了&#039;,&lt;br /&gt;
            unknown: &#039;未確認&#039;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        const statusSelect = form.querySelector(&lt;br /&gt;
            &#039;select[name=&amp;quot;StallMenuItem[status]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (statusSelect) {&lt;br /&gt;
            Array.from(statusSelect.options).forEach(&lt;br /&gt;
                function (option) {&lt;br /&gt;
                    if (statusLabels[option.value]) {&lt;br /&gt;
                        option.textContent =&lt;br /&gt;
                            statusLabels[option.value];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        console.log(&lt;br /&gt;
            &#039;商品マスター入力チェックを初期化しました。&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            setupStallMenuItemValidation&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        setupStallMenuItemValidation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(&lt;br /&gt;
        setupStallMenuItemValidation&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * StallMenuItem&lt;br /&gt;
 * 同一屋台 + 同一商品名の重複警告&lt;br /&gt;
 *&lt;br /&gt;
 * 保存は禁止しない。&lt;br /&gt;
 */&lt;br /&gt;
mw.loader.using([&lt;br /&gt;
    &#039;mediawiki.api&#039;,&lt;br /&gt;
    &#039;mediawiki.util&#039;&lt;br /&gt;
]).then(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    function cargoQuote(value) {&lt;br /&gt;
        return &amp;quot;&#039;&amp;quot; + String(value)&lt;br /&gt;
            .replace(/\\/g, &#039;\\\\&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &amp;quot;\\&#039;&amp;quot;) + &amp;quot;&#039;&amp;quot;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cargoRows(response) {&lt;br /&gt;
        return (response.cargoquery || []).map(&lt;br /&gt;
            function (row) {&lt;br /&gt;
                return row.title || row;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cargoQuery(&lt;br /&gt;
        tables,&lt;br /&gt;
        fields,&lt;br /&gt;
        where,&lt;br /&gt;
        limit&lt;br /&gt;
    ) {&lt;br /&gt;
        return api.get({&lt;br /&gt;
            action: &#039;cargoquery&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            tables: tables,&lt;br /&gt;
            fields: fields,&lt;br /&gt;
            where: where,&lt;br /&gt;
            limit: String(limit || 20)&lt;br /&gt;
        }).then(&lt;br /&gt;
            function (response) {&lt;br /&gt;
                return cargoRows(response);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setupStallMenuItemDuplicateWarning() {&lt;br /&gt;
        const form =&lt;br /&gt;
            document.getElementById(&#039;pfForm&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!form) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * StallMenuItemフォームだけを対象にする。&lt;br /&gt;
         */&lt;br /&gt;
        const stallSelect = form.querySelector(&lt;br /&gt;
            &#039;select[name=&amp;quot;StallMenuItem[stall_id]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        const nameInput = form.querySelector(&lt;br /&gt;
            &#039;input[name=&amp;quot;StallMenuItem[name]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!stallSelect || !nameInput) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 二重初期化防止&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            form.dataset&lt;br /&gt;
                .stallMenuItemDuplicateWarning ===&lt;br /&gt;
            &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        form.dataset&lt;br /&gt;
            .stallMenuItemDuplicateWarning =&lt;br /&gt;
            &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 警告表示欄&lt;br /&gt;
         */&lt;br /&gt;
        const warning =&lt;br /&gt;
            document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
        warning.className =&lt;br /&gt;
            &#039;stall-menu-item-duplicate-warning&#039;;&lt;br /&gt;
&lt;br /&gt;
        warning.setAttribute(&lt;br /&gt;
            &#039;role&#039;,&lt;br /&gt;
            &#039;status&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        warning.hidden = true;&lt;br /&gt;
&lt;br /&gt;
        warning.style.marginTop = &#039;8px&#039;;&lt;br /&gt;
        warning.style.padding = &#039;10px&#039;;&lt;br /&gt;
        warning.style.border = &#039;1px solid #a2a9b1&#039;;&lt;br /&gt;
        warning.style.borderRadius = &#039;4px&#039;;&lt;br /&gt;
&lt;br /&gt;
        const container =&lt;br /&gt;
            nameInput.closest(&#039;td&#039;) ||&lt;br /&gt;
            nameInput.parentNode;&lt;br /&gt;
&lt;br /&gt;
        container.appendChild(warning);&lt;br /&gt;
&lt;br /&gt;
        let timer = null;&lt;br /&gt;
        let requestSerial = 0;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Page Formsのmappingでは&lt;br /&gt;
         * SELECT.valueが屋台名になる場合があるため、&lt;br /&gt;
         * Cargoからstall_idを解決する。&lt;br /&gt;
         */&lt;br /&gt;
        function resolveStallId() {&lt;br /&gt;
            const rawValue =&lt;br /&gt;
                String(&lt;br /&gt;
                    stallSelect.value || &#039;&#039;&lt;br /&gt;
                ).trim();&lt;br /&gt;
&lt;br /&gt;
            if (!rawValue) {&lt;br /&gt;
                return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 数値ならそのまま使用。&lt;br /&gt;
             */&lt;br /&gt;
            if (/^\d+$/.test(rawValue)) {&lt;br /&gt;
                return Promise.resolve(&lt;br /&gt;
                    rawValue&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const selectedOption =&lt;br /&gt;
                stallSelect.options[&lt;br /&gt;
                    stallSelect.selectedIndex&lt;br /&gt;
                ];&lt;br /&gt;
&lt;br /&gt;
            const selectedText =&lt;br /&gt;
                selectedOption&lt;br /&gt;
                    ? selectedOption.textContent.trim()&lt;br /&gt;
                    : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            const names = [];&lt;br /&gt;
&lt;br /&gt;
            if (rawValue) {&lt;br /&gt;
                names.push(rawValue);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                selectedText &amp;amp;&amp;amp;&lt;br /&gt;
                names.indexOf(selectedText) === -1&lt;br /&gt;
            ) {&lt;br /&gt;
                names.push(selectedText);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (names.length === 0) {&lt;br /&gt;
                return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const where = names.map(&lt;br /&gt;
                function (name) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        &#039;name=&#039; +&lt;br /&gt;
                        cargoQuote(name)&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            ).join(&#039; OR &#039;);&lt;br /&gt;
&lt;br /&gt;
            return cargoQuery(&lt;br /&gt;
                &#039;Stalls&#039;,&lt;br /&gt;
                &#039;stall_id=stall_id,&#039; +&lt;br /&gt;
                    &#039;name=stall_name&#039;,&lt;br /&gt;
                where,&lt;br /&gt;
                10&lt;br /&gt;
            ).then(&lt;br /&gt;
                function (rows) {&lt;br /&gt;
                    if (!rows.length) {&lt;br /&gt;
                        return &#039;&#039;;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return String(&lt;br /&gt;
                        rows[0].stall_id || &#039;&#039;&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function clearWarning() {&lt;br /&gt;
            warning.hidden = true;&lt;br /&gt;
            warning.textContent = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function showWarning(rows) {&lt;br /&gt;
            warning.textContent = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            const title =&lt;br /&gt;
                document.createElement(&#039;strong&#039;);&lt;br /&gt;
&lt;br /&gt;
            title.textContent =&lt;br /&gt;
                &#039;同じ屋台に同名の商品がすでに登録されています。&#039;;&lt;br /&gt;
&lt;br /&gt;
            warning.appendChild(title);&lt;br /&gt;
&lt;br /&gt;
            const text =&lt;br /&gt;
                document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
            text.textContent =&lt;br /&gt;
                &#039;重複登録でないか既存商品を確認してください。保存自体は禁止しません。&#039;;&lt;br /&gt;
&lt;br /&gt;
            warning.appendChild(text);&lt;br /&gt;
&lt;br /&gt;
            const list =&lt;br /&gt;
                document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
            rows.forEach(&lt;br /&gt;
                function (row) {&lt;br /&gt;
                    const item =&lt;br /&gt;
                        document.createElement(&#039;li&#039;);&lt;br /&gt;
&lt;br /&gt;
                    const link =&lt;br /&gt;
                        document.createElement(&#039;a&#039;);&lt;br /&gt;
&lt;br /&gt;
                    link.href =&lt;br /&gt;
                        mw.util.getUrl(&lt;br /&gt;
                            row.page_name&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    link.textContent =&lt;br /&gt;
                        (&lt;br /&gt;
                            row.menu_name ||&lt;br /&gt;
                            &#039;商品&#039;&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039;（商品ID: &#039; +&lt;br /&gt;
                        row.menu_item_id +&lt;br /&gt;
                        &#039;）&#039;;&lt;br /&gt;
&lt;br /&gt;
                    link.target = &#039;_blank&#039;;&lt;br /&gt;
&lt;br /&gt;
                    item.appendChild(link);&lt;br /&gt;
                    list.appendChild(item);&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            warning.appendChild(list);&lt;br /&gt;
            warning.hidden = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function checkDuplicate() {&lt;br /&gt;
            const menuName =&lt;br /&gt;
                nameInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !stallSelect.value ||&lt;br /&gt;
                !menuName&lt;br /&gt;
            ) {&lt;br /&gt;
                clearWarning();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const currentRequest =&lt;br /&gt;
                ++requestSerial;&lt;br /&gt;
&lt;br /&gt;
            resolveStallId().then(&lt;br /&gt;
                function (stallId) {&lt;br /&gt;
                    if (&lt;br /&gt;
                        currentRequest !==&lt;br /&gt;
                        requestSerial&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    if (!stallId) {&lt;br /&gt;
                        clearWarning();&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return cargoQuery(&lt;br /&gt;
                        &#039;StallMenuItems&#039;,&lt;br /&gt;
                        &#039;menu_item_id=menu_item_id,&#039; +&lt;br /&gt;
                            &#039;name=menu_name,&#039; +&lt;br /&gt;
                            &#039;_pageName=page_name&#039;,&lt;br /&gt;
                        &#039;stall_id=&#039; +&lt;br /&gt;
                            stallId +&lt;br /&gt;
                            &#039; AND name=&#039; +&lt;br /&gt;
                            cargoQuote(&lt;br /&gt;
                                menuName&lt;br /&gt;
                            ),&lt;br /&gt;
                        20&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            ).then(&lt;br /&gt;
                function (rows) {&lt;br /&gt;
                    if (&lt;br /&gt;
                        rows === null ||&lt;br /&gt;
                        rows === undefined&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        currentRequest !==&lt;br /&gt;
                        requestSerial&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * 編集画面では&lt;br /&gt;
                     * 自分自身を重複候補から除外。&lt;br /&gt;
                     */&lt;br /&gt;
                    const currentPage =&lt;br /&gt;
                        normalizePageName(&lt;br /&gt;
                            mw.config.get(&lt;br /&gt;
                                &#039;wgPageName&#039;&lt;br /&gt;
                            )&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    const duplicates =&lt;br /&gt;
                        rows.filter(&lt;br /&gt;
                            function (row) {&lt;br /&gt;
                                return (&lt;br /&gt;
                                    normalizePageName(&lt;br /&gt;
                                        row.page_name&lt;br /&gt;
                                    ) !==&lt;br /&gt;
                                    currentPage&lt;br /&gt;
                                );&lt;br /&gt;
                            }&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        duplicates.length === 0&lt;br /&gt;
                    ) {&lt;br /&gt;
                        clearWarning();&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    showWarning(&lt;br /&gt;
                        duplicates&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            ).catch(&lt;br /&gt;
                function (error) {&lt;br /&gt;
                    console.error(&lt;br /&gt;
                        &#039;商品重複確認に失敗しました。&#039;,&lt;br /&gt;
                        error&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    clearWarning();&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function scheduleCheck() {&lt;br /&gt;
            window.clearTimeout(timer);&lt;br /&gt;
&lt;br /&gt;
            timer =&lt;br /&gt;
                window.setTimeout(&lt;br /&gt;
                    checkDuplicate,&lt;br /&gt;
                    300&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        stallSelect.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            scheduleCheck&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        nameInput.addEventListener(&lt;br /&gt;
            &#039;input&#039;,&lt;br /&gt;
            scheduleCheck&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        nameInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            scheduleCheck&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 編集画面で既存値が入っている場合にも確認。&lt;br /&gt;
         */&lt;br /&gt;
        scheduleCheck();&lt;br /&gt;
&lt;br /&gt;
        console.log(&lt;br /&gt;
            &#039;商品重複警告を初期化しました。&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState ===&lt;br /&gt;
        &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            setupStallMenuItemDuplicateWarning&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        setupStallMenuItemDuplicateWarning();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&lt;br /&gt;
        &#039;wikipage.content&#039;&lt;br /&gt;
    ).add(&lt;br /&gt;
        setupStallMenuItemDuplicateWarning&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * Venue：緯度・経度バリデーション&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    const latitudeInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;Venue[latitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    const longitudeInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;Venue[longitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    function setupVenueCoordinateValidation(&lt;br /&gt;
        input,&lt;br /&gt;
        label,&lt;br /&gt;
        min,&lt;br /&gt;
        max&lt;br /&gt;
    ) {&lt;br /&gt;
        if (!input) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        input.inputMode = &#039;decimal&#039;;&lt;br /&gt;
&lt;br /&gt;
        const validateCoordinate = function () {&lt;br /&gt;
            const value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
            input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Venueでは緯度・経度は任意。&lt;br /&gt;
             * 空欄なら正常。&lt;br /&gt;
             */&lt;br /&gt;
            if (value === &#039;&#039;) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 数値形式チェック&lt;br /&gt;
             */&lt;br /&gt;
            if (!/^-?\d+(\.\d+)?$/.test(value)) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    label + &#039;は数値で入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 日本付近の範囲チェック&lt;br /&gt;
             */&lt;br /&gt;
            const number = Number(value);&lt;br /&gt;
&lt;br /&gt;
            if (number &amp;lt; min || number &amp;gt; max) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    label +&lt;br /&gt;
                    &#039;は&#039; +&lt;br /&gt;
                    min +&lt;br /&gt;
                    &#039;〜&#039; +&lt;br /&gt;
                    max +&lt;br /&gt;
                    &#039;の範囲で入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&lt;br /&gt;
            &#039;input&#039;,&lt;br /&gt;
            validateCoordinate&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            validateCoordinate&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&lt;br /&gt;
            &#039;invalid&#039;,&lt;br /&gt;
            validateCoordinate&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        validateCoordinate();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    setupVenueCoordinateValidation(&lt;br /&gt;
        latitudeInput,&lt;br /&gt;
        &#039;緯度&#039;,&lt;br /&gt;
        20,&lt;br /&gt;
        46&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    setupVenueCoordinateValidation(&lt;br /&gt;
        longitudeInput,&lt;br /&gt;
        &#039;経度&#039;,&lt;br /&gt;
        122,&lt;br /&gt;
        154&lt;br /&gt;
    );&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * Venue：地図ピン → 緯度・経度&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    const latInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;Venue[latitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    const lonInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;Venue[longitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (!latInput || !lonInput) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using(&#039;ext.pageforms.leaflet&#039;).then(function () {&lt;br /&gt;
        if (&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                &#039;matsuri-venue-location-map&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const mapDiv = document.createElement(&#039;div&#039;);&lt;br /&gt;
        mapDiv.id = &#039;matsuri-venue-location-map&#039;;&lt;br /&gt;
        mapDiv.style.height = &#039;400px&#039;;&lt;br /&gt;
        mapDiv.style.width = &#039;100%&#039;;&lt;br /&gt;
        mapDiv.style.marginBottom = &#039;8px&#039;;&lt;br /&gt;
&lt;br /&gt;
        const help = document.createElement(&#039;div&#039;);&lt;br /&gt;
        help.textContent =&lt;br /&gt;
            &#039;地図をクリックして会場位置を指定してください。ピンはドラッグして微調整できます。&#039;;&lt;br /&gt;
        help.style.marginBottom = &#039;8px&#039;;&lt;br /&gt;
&lt;br /&gt;
        const wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
        wrapper.appendChild(help);&lt;br /&gt;
        wrapper.appendChild(mapDiv);&lt;br /&gt;
&lt;br /&gt;
        const latRow = latInput.closest(&#039;tr&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!latRow || !latRow.parentNode) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const mapRow = document.createElement(&#039;tr&#039;);&lt;br /&gt;
&lt;br /&gt;
        const th = document.createElement(&#039;th&#039;);&lt;br /&gt;
        th.textContent = &#039;会場位置を地図から選択&#039;;&lt;br /&gt;
&lt;br /&gt;
        const td = document.createElement(&#039;td&#039;);&lt;br /&gt;
        td.appendChild(wrapper);&lt;br /&gt;
&lt;br /&gt;
        mapRow.appendChild(th);&lt;br /&gt;
        mapRow.appendChild(td);&lt;br /&gt;
&lt;br /&gt;
        latRow.parentNode.insertBefore(&lt;br /&gt;
            mapRow,&lt;br /&gt;
            latRow&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        const hasCoordinates =&lt;br /&gt;
            latInput.value.trim() !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            lonInput.value.trim() !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !Number.isNaN(Number(latInput.value)) &amp;amp;&amp;amp;&lt;br /&gt;
            !Number.isNaN(Number(lonInput.value));&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 既存座標があればそこを表示。&lt;br /&gt;
         * 新規・座標未登録なら日本全体を表示。&lt;br /&gt;
         */&lt;br /&gt;
        const initialLat = hasCoordinates&lt;br /&gt;
            ? Number(latInput.value)&lt;br /&gt;
            : 36.2048;&lt;br /&gt;
&lt;br /&gt;
        const initialLon = hasCoordinates&lt;br /&gt;
            ? Number(lonInput.value)&lt;br /&gt;
            : 138.2529;&lt;br /&gt;
&lt;br /&gt;
        const map = L.map(mapDiv).setView(&lt;br /&gt;
            [initialLat, initialLon],&lt;br /&gt;
            hasCoordinates ? 17 : 5&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        L.tileLayer(&lt;br /&gt;
            &#039;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#039;,&lt;br /&gt;
            {&lt;br /&gt;
                maxZoom: 19,&lt;br /&gt;
                attribution:&lt;br /&gt;
                    &#039;&amp;amp;copy; OpenStreetMap contributors&#039;&lt;br /&gt;
            }&lt;br /&gt;
        ).addTo(map);&lt;br /&gt;
&lt;br /&gt;
        let marker = null;&lt;br /&gt;
&lt;br /&gt;
        function updateInputs(lat, lon) {&lt;br /&gt;
            const latValue =&lt;br /&gt;
                Number(lat).toFixed(6);&lt;br /&gt;
&lt;br /&gt;
            const lonValue =&lt;br /&gt;
                Number(lon).toFixed(6);&lt;br /&gt;
&lt;br /&gt;
            latInput.value = latValue;&lt;br /&gt;
            lonInput.value = lonValue;&lt;br /&gt;
&lt;br /&gt;
            latInput.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;input&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            lonInput.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;input&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            latInput.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;change&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            lonInput.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;change&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function placeMarker(latlng) {&lt;br /&gt;
            if (marker) {&lt;br /&gt;
                marker.setLatLng(latlng);&lt;br /&gt;
            } else {&lt;br /&gt;
                marker = L.marker(&lt;br /&gt;
                    latlng,&lt;br /&gt;
                    {&lt;br /&gt;
                        draggable: true&lt;br /&gt;
                    }&lt;br /&gt;
                ).addTo(map);&lt;br /&gt;
&lt;br /&gt;
                marker.on(&lt;br /&gt;
                    &#039;dragend&#039;,&lt;br /&gt;
                    function () {&lt;br /&gt;
                        const position =&lt;br /&gt;
                            marker.getLatLng();&lt;br /&gt;
&lt;br /&gt;
                        updateInputs(&lt;br /&gt;
                            position.lat,&lt;br /&gt;
                            position.lng&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            updateInputs(&lt;br /&gt;
                latlng.lat,&lt;br /&gt;
                latlng.lng&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hasCoordinates) {&lt;br /&gt;
            placeMarker({&lt;br /&gt;
                lat: initialLat,&lt;br /&gt;
                lng: initialLon&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        map.on(&lt;br /&gt;
            &#039;click&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                placeMarker(&lt;br /&gt;
                    event.latlng&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 緯度・経度を手動修正した場合も&lt;br /&gt;
         * ピンを同期する。&lt;br /&gt;
         */&lt;br /&gt;
        function syncMarkerFromInputs() {&lt;br /&gt;
            const lat =&lt;br /&gt;
                Number(latInput.value);&lt;br /&gt;
&lt;br /&gt;
            const lon =&lt;br /&gt;
                Number(lonInput.value);&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                latInput.value.trim() === &#039;&#039; ||&lt;br /&gt;
                lonInput.value.trim() === &#039;&#039; ||&lt;br /&gt;
                Number.isNaN(lat) ||&lt;br /&gt;
                Number.isNaN(lon)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const latlng = {&lt;br /&gt;
                lat: lat,&lt;br /&gt;
                lng: lon&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            if (marker) {&lt;br /&gt;
                marker.setLatLng(latlng);&lt;br /&gt;
            } else {&lt;br /&gt;
                marker = L.marker(&lt;br /&gt;
                    latlng,&lt;br /&gt;
                    {&lt;br /&gt;
                        draggable: true&lt;br /&gt;
                    }&lt;br /&gt;
                ).addTo(map);&lt;br /&gt;
&lt;br /&gt;
                marker.on(&lt;br /&gt;
                    &#039;dragend&#039;,&lt;br /&gt;
                    function () {&lt;br /&gt;
                        const position =&lt;br /&gt;
                            marker.getLatLng();&lt;br /&gt;
&lt;br /&gt;
                        updateInputs(&lt;br /&gt;
                            position.lat,&lt;br /&gt;
                            position.lng&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            map.setView(&lt;br /&gt;
                [lat, lon],&lt;br /&gt;
                17&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        latInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            syncMarkerFromInputs&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        lonInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            syncMarkerFromInputs&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            map.invalidateSize();&lt;br /&gt;
        }, 100);&lt;br /&gt;
&lt;br /&gt;
        console.log(&lt;br /&gt;
            &#039;Venue地図ピン入力を初期化しました。&#039;&lt;br /&gt;
        );&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * FestivalStallPlacement：&lt;br /&gt;
 * 祭り → 会場候補連動&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    function setupFestivalVenueFilter() {&lt;br /&gt;
        const festivalSelect =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;select[name=&amp;quot;FestivalStallPlacement[festival_id]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const venueSelect =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;select[name=&amp;quot;FestivalStallPlacement[venue_id]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!festivalSelect || !venueSelect) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            venueSelect.dataset.r5FestivalVenueFilter ===&lt;br /&gt;
            &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        venueSelect.dataset.r5FestivalVenueFilter =&lt;br /&gt;
            &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
        const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
        const originalOptions =&lt;br /&gt;
            Array.from(&lt;br /&gt;
                venueSelect.options&lt;br /&gt;
            ).map(function (option) {&lt;br /&gt;
                return option.cloneNode(true);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        const initialFestival =&lt;br /&gt;
            festivalSelect.value.trim();&lt;br /&gt;
&lt;br /&gt;
        const initialVenue =&lt;br /&gt;
            venueSelect.value.trim();&lt;br /&gt;
&lt;br /&gt;
        let requestId = 0;&lt;br /&gt;
&lt;br /&gt;
        function escapeCargoValue(value) {&lt;br /&gt;
            return String(value).replace(&lt;br /&gt;
                /&#039;/g,&lt;br /&gt;
                &amp;quot;&#039;&#039;&amp;quot;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlankOption(label) {&lt;br /&gt;
            let blank =&lt;br /&gt;
                originalOptions.find(function (option) {&lt;br /&gt;
                    return option.value === &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
            if (blank) {&lt;br /&gt;
                blank=blank.cloneNode(true);&lt;br /&gt;
            } else {&lt;br /&gt;
                blank=document.createElement(&lt;br /&gt;
                    &#039;option&#039;&lt;br /&gt;
                );&lt;br /&gt;
                blank.value=&#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            blank.textContent=label;&lt;br /&gt;
&lt;br /&gt;
            return blank;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function findOriginalOption(value) {&lt;br /&gt;
            const option =&lt;br /&gt;
                originalOptions.find(&lt;br /&gt;
                    function (item) {&lt;br /&gt;
                        return item.value === value;&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            return option&lt;br /&gt;
                ? option.cloneNode(true)&lt;br /&gt;
                : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function dispatchVenueChange(&lt;br /&gt;
            preservePlacementCoordinates&lt;br /&gt;
        ) {&lt;br /&gt;
            venueSelect.dispatchEvent(&lt;br /&gt;
                new CustomEvent(&lt;br /&gt;
                    &#039;change&#039;,&lt;br /&gt;
                    {&lt;br /&gt;
                        bubbles: true,&lt;br /&gt;
                        detail: {&lt;br /&gt;
                            matsuriPreservePlacementCoordinates:&lt;br /&gt;
                                preservePlacementCoordinates === true&lt;br /&gt;
                        }&lt;br /&gt;
                    }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function replaceOptions(&lt;br /&gt;
            venuePages,&lt;br /&gt;
            preserveCurrent,&lt;br /&gt;
            preservePlacementCoordinates&lt;br /&gt;
        ) {&lt;br /&gt;
            const oldValue =&lt;br /&gt;
                preserveCurrent&lt;br /&gt;
                    ? initialVenue&lt;br /&gt;
                    : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            const fragment =&lt;br /&gt;
                document.createDocumentFragment();&lt;br /&gt;
&lt;br /&gt;
            fragment.appendChild(&lt;br /&gt;
                getBlankOption(&#039;未指定&#039;)&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            venuePages.forEach(function (page) {&lt;br /&gt;
                let option =&lt;br /&gt;
                    findOriginalOption(page);&lt;br /&gt;
&lt;br /&gt;
                if (!option) {&lt;br /&gt;
                    console.warn(&lt;br /&gt;
                        &#039;Page Formsの元候補に会場がありません。&#039;,&lt;br /&gt;
                        page&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                option.selected=false;&lt;br /&gt;
                fragment.appendChild(option);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                preserveCurrent &amp;amp;&amp;amp;&lt;br /&gt;
                oldValue !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                !venuePages.includes(oldValue)&lt;br /&gt;
            ) {&lt;br /&gt;
                const currentOption =&lt;br /&gt;
                    findOriginalOption(oldValue);&lt;br /&gt;
&lt;br /&gt;
                if (currentOption) {&lt;br /&gt;
                    currentOption.textContent +=&lt;br /&gt;
                        &#039;（現在登録値）&#039;;&lt;br /&gt;
&lt;br /&gt;
                    fragment.appendChild(&lt;br /&gt;
                        currentOption&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            venueSelect.replaceChildren(&lt;br /&gt;
                fragment&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            let nextValue=&#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                preserveCurrent &amp;amp;&amp;amp;&lt;br /&gt;
                oldValue !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                Array.from(&lt;br /&gt;
                    venueSelect.options&lt;br /&gt;
                ).some(function (option) {&lt;br /&gt;
                    return option.value ===&lt;br /&gt;
                        oldValue;&lt;br /&gt;
                })&lt;br /&gt;
            ) {&lt;br /&gt;
                nextValue=oldValue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            venueSelect.value=nextValue;&lt;br /&gt;
            venueSelect.disabled=false;&lt;br /&gt;
&lt;br /&gt;
            dispatchVenueChange(&lt;br /&gt;
                preservePlacementCoordinates&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function showLoading() {&lt;br /&gt;
            venueSelect.replaceChildren(&lt;br /&gt;
                getBlankOption(&lt;br /&gt;
                    &#039;会場候補を読み込み中…&#039;&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            venueSelect.disabled=true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function showFailure(&lt;br /&gt;
            preserveCurrent,&lt;br /&gt;
            preservePlacementCoordinates&lt;br /&gt;
        ) {&lt;br /&gt;
            const fragment =&lt;br /&gt;
                document.createDocumentFragment();&lt;br /&gt;
&lt;br /&gt;
            fragment.appendChild(&lt;br /&gt;
                getBlankOption(&lt;br /&gt;
                    &#039;未指定（候補取得失敗）&#039;&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                preserveCurrent &amp;amp;&amp;amp;&lt;br /&gt;
                initialVenue !== &#039;&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                const current =&lt;br /&gt;
                    findOriginalOption(&lt;br /&gt;
                        initialVenue&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (current) {&lt;br /&gt;
                    current.textContent +=&lt;br /&gt;
                        &#039;（現在登録値）&#039;;&lt;br /&gt;
&lt;br /&gt;
                    current.selected=true;&lt;br /&gt;
&lt;br /&gt;
                    fragment.appendChild(&lt;br /&gt;
                        current&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            venueSelect.replaceChildren(&lt;br /&gt;
                fragment&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            venueSelect.disabled=false;&lt;br /&gt;
&lt;br /&gt;
            dispatchVenueChange(&lt;br /&gt;
                preservePlacementCoordinates&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function loadVenues(&lt;br /&gt;
            preserveCurrent,&lt;br /&gt;
            preservePlacementCoordinates&lt;br /&gt;
        ) {&lt;br /&gt;
            const festivalValue =&lt;br /&gt;
                festivalSelect.value.trim();&lt;br /&gt;
&lt;br /&gt;
            const currentRequest =&lt;br /&gt;
                ++requestId;&lt;br /&gt;
&lt;br /&gt;
            if (festivalValue === &#039;&#039;) {&lt;br /&gt;
                venueSelect.replaceChildren(&lt;br /&gt;
                    getBlankOption(&#039;未指定&#039;)&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                venueSelect.disabled=false;&lt;br /&gt;
&lt;br /&gt;
                dispatchVenueChange(&lt;br /&gt;
                    preservePlacementCoordinates&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            showLoading();&lt;br /&gt;
&lt;br /&gt;
            const escaped =&lt;br /&gt;
                escapeCargoValue(&lt;br /&gt;
                    festivalValue&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action:&#039;cargoquery&#039;,&lt;br /&gt;
                format:&#039;json&#039;,&lt;br /&gt;
                tables:&lt;br /&gt;
                    &#039;Festivals=F,&#039; +&lt;br /&gt;
                    &#039;FestivalVenues=FV,&#039; +&lt;br /&gt;
                    &#039;Venues=V&#039;,&lt;br /&gt;
                fields:&lt;br /&gt;
                    &#039;V._pageName=venue_page,&#039; +&lt;br /&gt;
                    &#039;V.name=venue_name,&#039; +&lt;br /&gt;
                    &#039;FV.sort_order=sort_order&#039;,&lt;br /&gt;
                join_on:&lt;br /&gt;
                    &#039;F.festival_id=FV.festival_id,&#039; +&lt;br /&gt;
                    &#039;FV.venue_id=V.venue_id&#039;,&lt;br /&gt;
                where:&lt;br /&gt;
                    &amp;quot;(&amp;quot; +&lt;br /&gt;
                    &amp;quot;F.name=&#039;&amp;quot; +&lt;br /&gt;
                    escaped +&lt;br /&gt;
                    &amp;quot;&#039; OR &amp;quot; +&lt;br /&gt;
                    &amp;quot;F._pageName=&#039;&amp;quot; +&lt;br /&gt;
                    escaped +&lt;br /&gt;
                    &amp;quot;&#039;&amp;quot; +&lt;br /&gt;
                    &amp;quot;)&amp;quot;,&lt;br /&gt;
                order_by:&lt;br /&gt;
                    &#039;FV.sort_order ASC,&#039; +&lt;br /&gt;
                    &#039;V.name ASC&#039;,&lt;br /&gt;
                limit:&#039;100&#039;&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                if (&lt;br /&gt;
                    currentRequest !==&lt;br /&gt;
                    requestId&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const rows =&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    Array.isArray(&lt;br /&gt;
                        data.cargoquery&lt;br /&gt;
                    )&lt;br /&gt;
                        ? data.cargoquery&lt;br /&gt;
                        : [];&lt;br /&gt;
&lt;br /&gt;
                const venuePages=[];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (result) {&lt;br /&gt;
                    const row =&lt;br /&gt;
                        result.title ||&lt;br /&gt;
                        result;&lt;br /&gt;
&lt;br /&gt;
                    const page =&lt;br /&gt;
                        row.venue_page ===&lt;br /&gt;
                            undefined ||&lt;br /&gt;
                        row.venue_page ===&lt;br /&gt;
                            null&lt;br /&gt;
                            ? &#039;&#039;&lt;br /&gt;
                            : String(&lt;br /&gt;
                                row.venue_page&lt;br /&gt;
                            ).trim();&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                        !venuePages.includes(page)&lt;br /&gt;
                    ) {&lt;br /&gt;
                        venuePages.push(page);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                replaceOptions(&lt;br /&gt;
                    venuePages,&lt;br /&gt;
                    preserveCurrent,&lt;br /&gt;
                    preservePlacementCoordinates&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                console.log(&lt;br /&gt;
                    &#039;祭り連動会場候補を更新しました。&#039;,&lt;br /&gt;
                    {&lt;br /&gt;
                        festival:&lt;br /&gt;
                            festivalValue,&lt;br /&gt;
                        venues:&lt;br /&gt;
                            venuePages&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
            }).catch(function (error) {&lt;br /&gt;
                if (&lt;br /&gt;
                    currentRequest !==&lt;br /&gt;
                    requestId&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                console.error(&lt;br /&gt;
                    &#039;祭り連動会場候補の取得に失敗しました。&#039;,&lt;br /&gt;
                    error&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                showFailure(&lt;br /&gt;
                    preserveCurrent,&lt;br /&gt;
                    preservePlacementCoordinates&lt;br /&gt;
                );&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        festivalSelect.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            function () {&lt;br /&gt;
                loadVenues(&lt;br /&gt;
                    false,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        loadVenues(&lt;br /&gt;
            festivalSelect.value.trim() ===&lt;br /&gt;
                initialFestival &amp;amp;&amp;amp;&lt;br /&gt;
            initialVenue !== &#039;&#039;,&lt;br /&gt;
            true&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    setupFestivalVenueFilter();&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&lt;br /&gt;
        &#039;pf.formSetupAfter&#039;&lt;br /&gt;
    ).add(&lt;br /&gt;
        setupFestivalVenueFilter&lt;br /&gt;
    );&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * FestivalStallPlacement：&lt;br /&gt;
 * 会場連動地図ピン → 緯度・経度&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    const venueSelect = document.querySelector(&lt;br /&gt;
        &#039;select[name=&amp;quot;FestivalStallPlacement[venue_id]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    const latInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalStallPlacement[latitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    const lonInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalStallPlacement[longitude]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        !venueSelect ||&lt;br /&gt;
        !latInput ||&lt;br /&gt;
        !lonInput&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using(&lt;br /&gt;
        &#039;ext.pageforms.leaflet&#039;&lt;br /&gt;
    ).then(function () {&lt;br /&gt;
        if (&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                &#039;matsuri-placement-location-map&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
        const mapDiv =&lt;br /&gt;
            document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
        mapDiv.id =&lt;br /&gt;
            &#039;matsuri-placement-location-map&#039;;&lt;br /&gt;
&lt;br /&gt;
        mapDiv.style.height = &#039;400px&#039;;&lt;br /&gt;
        mapDiv.style.width = &#039;100%&#039;;&lt;br /&gt;
        mapDiv.style.marginBottom = &#039;8px&#039;;&lt;br /&gt;
&lt;br /&gt;
        const help =&lt;br /&gt;
            document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
        help.textContent =&lt;br /&gt;
            &#039;会場を選択すると会場周辺を表示します。&#039; +&lt;br /&gt;
            &#039;地図をクリックして実際の屋台位置を指定してください。&#039; +&lt;br /&gt;
            &#039;ピンはドラッグして微調整できます。&#039;;&lt;br /&gt;
&lt;br /&gt;
        help.style.marginBottom = &#039;8px&#039;;&lt;br /&gt;
&lt;br /&gt;
        const wrapper =&lt;br /&gt;
            document.createElement(&#039;div&#039;);&lt;br /&gt;
&lt;br /&gt;
        wrapper.appendChild(help);&lt;br /&gt;
        wrapper.appendChild(mapDiv);&lt;br /&gt;
&lt;br /&gt;
        const latRow =&lt;br /&gt;
            latInput.closest(&#039;tr&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !latRow ||&lt;br /&gt;
            !latRow.parentNode&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const mapRow =&lt;br /&gt;
            document.createElement(&#039;tr&#039;);&lt;br /&gt;
&lt;br /&gt;
        const th =&lt;br /&gt;
            document.createElement(&#039;th&#039;);&lt;br /&gt;
&lt;br /&gt;
        th.textContent =&lt;br /&gt;
            &#039;出店位置を地図から選択&#039;;&lt;br /&gt;
&lt;br /&gt;
        const td =&lt;br /&gt;
            document.createElement(&#039;td&#039;);&lt;br /&gt;
&lt;br /&gt;
        td.appendChild(wrapper);&lt;br /&gt;
&lt;br /&gt;
        mapRow.appendChild(th);&lt;br /&gt;
        mapRow.appendChild(td);&lt;br /&gt;
&lt;br /&gt;
        latRow.parentNode.insertBefore(&lt;br /&gt;
            mapRow,&lt;br /&gt;
            latRow&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 初期状態は日本全体。&lt;br /&gt;
         *&lt;br /&gt;
         * 既存Placementに座標がある場合は&lt;br /&gt;
         * 後でその位置へ移動する。&lt;br /&gt;
         */&lt;br /&gt;
        const map = L.map(&lt;br /&gt;
            mapDiv&lt;br /&gt;
        ).setView(&lt;br /&gt;
            [ 36.2048, 138.2529 ],&lt;br /&gt;
            5&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        L.tileLayer(&lt;br /&gt;
            &#039;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&#039;,&lt;br /&gt;
            {&lt;br /&gt;
                maxZoom: 19,&lt;br /&gt;
                attribution:&lt;br /&gt;
                    &#039;&amp;amp;copy; OpenStreetMap contributors&#039;&lt;br /&gt;
            }&lt;br /&gt;
        ).addTo(map);&lt;br /&gt;
&lt;br /&gt;
        let marker = null;&lt;br /&gt;
        let venueRequestId = 0;&lt;br /&gt;
&lt;br /&gt;
        function dispatchInputEvents(input) {&lt;br /&gt;
            input.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;input&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            input.dispatchEvent(&lt;br /&gt;
                new Event(&lt;br /&gt;
                    &#039;change&#039;,&lt;br /&gt;
                    { bubbles: true }&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function updateInputs(lat, lon) {&lt;br /&gt;
            latInput.value =&lt;br /&gt;
                Number(lat).toFixed(6);&lt;br /&gt;
&lt;br /&gt;
            lonInput.value =&lt;br /&gt;
                Number(lon).toFixed(6);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 既存の必須・日本範囲チェックを&lt;br /&gt;
             * そのまま発火させる。&lt;br /&gt;
             */&lt;br /&gt;
            dispatchInputEvents(latInput);&lt;br /&gt;
            dispatchInputEvents(lonInput);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * R10-5C ISSUE-06B:&lt;br /&gt;
         * PageForms配下のLeaflet default PNGは&lt;br /&gt;
         * この環境ではHTMLへredirectされるため、&lt;br /&gt;
         * 外部画像に依存しないdivIconを使用。&lt;br /&gt;
         */&lt;br /&gt;
        const placementMarkerIcon =&lt;br /&gt;
            L.divIcon({&lt;br /&gt;
                className:&lt;br /&gt;
                    &#039;matsuri-placement-marker-icon&#039;,&lt;br /&gt;
&lt;br /&gt;
                html:&lt;br /&gt;
                    &#039;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; &#039; +&lt;br /&gt;
                    &#039;width=&amp;quot;28&amp;quot; height=&amp;quot;42&amp;quot; viewBox=&amp;quot;0 0 28 42&amp;quot; &#039; +&lt;br /&gt;
                    &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;path d=&amp;quot;M14 1C6.8 1 1 6.8 1 14c0 10 13 27 13 27s13-17 13-27C27 6.8 21.2 1 14 1Z&amp;quot; &#039; +&lt;br /&gt;
                    &#039;fill=&amp;quot;#2a81cb&amp;quot; stroke=&amp;quot;#ffffff&amp;quot; stroke-width=&amp;quot;2&amp;quot;/&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;circle cx=&amp;quot;14&amp;quot; cy=&amp;quot;14&amp;quot; r=&amp;quot;5&amp;quot; fill=&amp;quot;#ffffff&amp;quot;/&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/svg&amp;gt;&#039;,&lt;br /&gt;
&lt;br /&gt;
                iconSize:&lt;br /&gt;
                    [&lt;br /&gt;
                        28,&lt;br /&gt;
                        42&lt;br /&gt;
                    ],&lt;br /&gt;
&lt;br /&gt;
                iconAnchor:&lt;br /&gt;
                    [&lt;br /&gt;
                        14,&lt;br /&gt;
                        40&lt;br /&gt;
                    ]&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        function createMarker(latlng) {&lt;br /&gt;
            marker = L.marker(&lt;br /&gt;
                latlng,&lt;br /&gt;
                {&lt;br /&gt;
                    draggable:&lt;br /&gt;
                        true,&lt;br /&gt;
&lt;br /&gt;
                    icon:&lt;br /&gt;
                        placementMarkerIcon&lt;br /&gt;
                }&lt;br /&gt;
            ).addTo(map);&lt;br /&gt;
&lt;br /&gt;
            marker.on(&lt;br /&gt;
                &#039;dragend&#039;,&lt;br /&gt;
                function () {&lt;br /&gt;
                    const position =&lt;br /&gt;
                        marker.getLatLng();&lt;br /&gt;
&lt;br /&gt;
                    updateInputs(&lt;br /&gt;
                        position.lat,&lt;br /&gt;
                        position.lng&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function placeMarker(latlng) {&lt;br /&gt;
            if (marker) {&lt;br /&gt;
                marker.setLatLng(latlng);&lt;br /&gt;
            } else {&lt;br /&gt;
                createMarker(latlng);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            updateInputs(&lt;br /&gt;
                latlng.lat,&lt;br /&gt;
                latlng.lng&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function removeMarker() {&lt;br /&gt;
            if (!marker) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            map.removeLayer(marker);&lt;br /&gt;
            marker = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function clearCoordinates() {&lt;br /&gt;
            latInput.value = &#039;&#039;;&lt;br /&gt;
            lonInput.value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            dispatchInputEvents(latInput);&lt;br /&gt;
            dispatchInputEvents(lonInput);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getCurrentCoordinates() {&lt;br /&gt;
            const lat =&lt;br /&gt;
                Number(latInput.value);&lt;br /&gt;
&lt;br /&gt;
            const lon =&lt;br /&gt;
                Number(lonInput.value);&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                latInput.value.trim() === &#039;&#039; ||&lt;br /&gt;
                lonInput.value.trim() === &#039;&#039; ||&lt;br /&gt;
                Number.isNaN(lat) ||&lt;br /&gt;
                Number.isNaN(lon)&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                lat: lat,&lt;br /&gt;
                lng: lon&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function escapeCargoValue(value) {&lt;br /&gt;
            return String(value)&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /&#039;/g,&lt;br /&gt;
                    &amp;quot;&#039;&#039;&amp;quot;&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 選択されたVenueの座標へ&lt;br /&gt;
         * 地図だけ移動する。&lt;br /&gt;
         *&lt;br /&gt;
         * Placementのlatitude/longitudeには&lt;br /&gt;
         * コピーしない。&lt;br /&gt;
         */&lt;br /&gt;
        function centerOnVenue() {&lt;br /&gt;
            const venuePage =&lt;br /&gt;
                venueSelect.value.trim();&lt;br /&gt;
&lt;br /&gt;
            if (venuePage === &#039;&#039;) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const currentRequest =&lt;br /&gt;
                ++venueRequestId;&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;cargoquery&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                tables: &#039;Venues&#039;,&lt;br /&gt;
                fields:&lt;br /&gt;
                    &#039;venue_id=venue_id,&#039; +&lt;br /&gt;
                    &#039;_pageName=page_name,&#039; +&lt;br /&gt;
                    &#039;latitude=latitude,&#039; +&lt;br /&gt;
                    &#039;longitude=longitude&#039;,&lt;br /&gt;
                where:&lt;br /&gt;
                    &amp;quot;_pageName=&#039;&amp;quot; +&lt;br /&gt;
                    escapeCargoValue(&lt;br /&gt;
                        venuePage&lt;br /&gt;
                    ) +&lt;br /&gt;
                    &amp;quot;&#039;&amp;quot;,&lt;br /&gt;
                limit: &#039;1&#039;&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * 連続して会場を変更した場合、&lt;br /&gt;
                 * 古いレスポンスを無視する。&lt;br /&gt;
                 */&lt;br /&gt;
                if (&lt;br /&gt;
                    currentRequest !==&lt;br /&gt;
                    venueRequestId&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const result =&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    Array.isArray(&lt;br /&gt;
                        data.cargoquery&lt;br /&gt;
                    )&lt;br /&gt;
                        ? data.cargoquery&lt;br /&gt;
                        : [];&lt;br /&gt;
&lt;br /&gt;
                if (result.length === 0) {&lt;br /&gt;
                    console.warn(&lt;br /&gt;
                        &#039;会場情報を取得できませんでした。&#039;,&lt;br /&gt;
                        venuePage&lt;br /&gt;
                    );&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const row =&lt;br /&gt;
                    result[0].title ||&lt;br /&gt;
                    result[0];&lt;br /&gt;
&lt;br /&gt;
                const lat =&lt;br /&gt;
                    Number(row.latitude);&lt;br /&gt;
&lt;br /&gt;
                const lon =&lt;br /&gt;
                    Number(row.longitude);&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    row.latitude === undefined ||&lt;br /&gt;
                    row.latitude === null ||&lt;br /&gt;
                    String(&lt;br /&gt;
                        row.latitude&lt;br /&gt;
                    ).trim() === &#039;&#039; ||&lt;br /&gt;
                    row.longitude === undefined ||&lt;br /&gt;
                    row.longitude === null ||&lt;br /&gt;
                    String(&lt;br /&gt;
                        row.longitude&lt;br /&gt;
                    ).trim() === &#039;&#039; ||&lt;br /&gt;
                    Number.isNaN(lat) ||&lt;br /&gt;
                    Number.isNaN(lon)&lt;br /&gt;
                ) {&lt;br /&gt;
                    console.warn(&lt;br /&gt;
                        &#039;選択した会場には座標が登録されていません。&#039;,&lt;br /&gt;
                        venuePage&lt;br /&gt;
                    );&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                map.setView(&lt;br /&gt;
                    [ lat, lon ],&lt;br /&gt;
                    18&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                console.log(&lt;br /&gt;
                    &#039;会場位置へ地図を移動しました。&#039;,&lt;br /&gt;
                    {&lt;br /&gt;
                        venue: venuePage,&lt;br /&gt;
                        latitude: lat,&lt;br /&gt;
                        longitude: lon&lt;br /&gt;
                    }&lt;br /&gt;
                );&lt;br /&gt;
            }).catch(function (error) {&lt;br /&gt;
                console.error(&lt;br /&gt;
                    &#039;会場座標の取得に失敗しました。&#039;,&lt;br /&gt;
                    error&lt;br /&gt;
                );&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 地図クリック&lt;br /&gt;
         */&lt;br /&gt;
        map.on(&lt;br /&gt;
            &#039;click&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                placeMarker(&lt;br /&gt;
                    event.latlng&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 手入力された場合もピンを同期。&lt;br /&gt;
         */&lt;br /&gt;
        function syncMarkerFromInputs() {&lt;br /&gt;
            const coordinates =&lt;br /&gt;
                getCurrentCoordinates();&lt;br /&gt;
&lt;br /&gt;
            if (!coordinates) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (marker) {&lt;br /&gt;
                marker.setLatLng(&lt;br /&gt;
                    coordinates&lt;br /&gt;
                );&lt;br /&gt;
            } else {&lt;br /&gt;
                createMarker(&lt;br /&gt;
                    coordinates&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            map.setView(&lt;br /&gt;
                [&lt;br /&gt;
                    coordinates.lat,&lt;br /&gt;
                    coordinates.lng&lt;br /&gt;
                ],&lt;br /&gt;
                18&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        latInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            syncMarkerFromInputs&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        lonInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            syncMarkerFromInputs&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 会場を変更した場合。&lt;br /&gt;
         *&lt;br /&gt;
         * 前の会場用の屋台座標を&lt;br /&gt;
         * 誤って残さないようクリアする。&lt;br /&gt;
         */&lt;br /&gt;
        venueSelect.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            function (event) {&lt;br /&gt;
                const preservePlacementCoordinates =&lt;br /&gt;
                    !!(&lt;br /&gt;
                        event &amp;amp;&amp;amp;&lt;br /&gt;
                        event.detail &amp;amp;&amp;amp;&lt;br /&gt;
                        event.detail&lt;br /&gt;
                            .matsuriPreservePlacementCoordinates ===&lt;br /&gt;
                            true&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    preservePlacementCoordinates&lt;br /&gt;
                ) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Existing Placement coordinates&lt;br /&gt;
                     * take priority over Venue center.&lt;br /&gt;
                     *&lt;br /&gt;
                     * If there are no Placement&lt;br /&gt;
                     * coordinates, Venue is still a&lt;br /&gt;
                     * useful map starting point.&lt;br /&gt;
                     */&lt;br /&gt;
                    if (&lt;br /&gt;
                        !getCurrentCoordinates()&lt;br /&gt;
                    ) {&lt;br /&gt;
                        centerOnVenue();&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                removeMarker();&lt;br /&gt;
                clearCoordinates();&lt;br /&gt;
                centerOnVenue();&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 編集時：&lt;br /&gt;
         * 既存Placement座標を優先。&lt;br /&gt;
         *&lt;br /&gt;
         * 新規時：&lt;br /&gt;
         * Venue座標へ地図を移動。&lt;br /&gt;
         */&lt;br /&gt;
        const initialCoordinates =&lt;br /&gt;
            getCurrentCoordinates();&lt;br /&gt;
&lt;br /&gt;
        if (initialCoordinates) {&lt;br /&gt;
            createMarker(&lt;br /&gt;
                initialCoordinates&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            map.setView(&lt;br /&gt;
                [&lt;br /&gt;
                    initialCoordinates.lat,&lt;br /&gt;
                    initialCoordinates.lng&lt;br /&gt;
                ],&lt;br /&gt;
                18&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            centerOnVenue();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * R10-5C ISSUE-06A:&lt;br /&gt;
         * 折りたたみ中はmapが0x0なので、&lt;br /&gt;
         * 実際に展開された後でも&lt;br /&gt;
         * Leafletの内部サイズを再計算する。&lt;br /&gt;
         */&lt;br /&gt;
        const mapCollapsible =&lt;br /&gt;
            mapDiv.closest(&lt;br /&gt;
                &#039;.mw-collapsible&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            mapCollapsible&lt;br /&gt;
        ) {&lt;br /&gt;
            $(&lt;br /&gt;
                mapCollapsible&lt;br /&gt;
            ).on(&lt;br /&gt;
                &#039;afterExpand.mw-collapsible&#039;,&lt;br /&gt;
                function () {&lt;br /&gt;
                    setTimeout(&lt;br /&gt;
                        function () {&lt;br /&gt;
                            map.invalidateSize();&lt;br /&gt;
                        },&lt;br /&gt;
                        0&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 初期状態ですでに表示されている&lt;br /&gt;
         * ケースの既存挙動も維持。&lt;br /&gt;
         */&lt;br /&gt;
        setTimeout(&lt;br /&gt;
            function () {&lt;br /&gt;
                map.invalidateSize();&lt;br /&gt;
            },&lt;br /&gt;
            100&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        console.log(&lt;br /&gt;
            &#039;出店位置地図ピン入力を初期化しました。&#039;&lt;br /&gt;
        );&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * Venue：公式サイトURLの形式チェック&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    const officialSiteInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;Venue[official_site]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (!officialSiteInput) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    officialSiteInput.inputMode = &#039;url&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateVenueOfficialSite = function () {&lt;br /&gt;
        const value = officialSiteInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        officialSiteInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 空欄は許可。&lt;br /&gt;
         */&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(value);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * http:// または https:// のみ許可。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                url.protocol !== &#039;http:&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                url.protocol !== &#039;https:&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                officialSiteInput.setCustomValidity(&lt;br /&gt;
                    &#039;公式サイトURLは http:// または https:// で始まるURLを入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        } catch (e) {&lt;br /&gt;
            officialSiteInput.setCustomValidity(&lt;br /&gt;
                &#039;公式サイトURLを正しいURL形式で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    officialSiteInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateVenueOfficialSite&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    officialSiteInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateVenueOfficialSite&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    officialSiteInput.addEventListener(&lt;br /&gt;
        &#039;invalid&#039;,&lt;br /&gt;
        validateVenueOfficialSite&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    validateVenueOfficialSite();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * Festival&lt;br /&gt;
 * 公式URLの形式チェック&lt;br /&gt;
 *&lt;br /&gt;
 * 空欄は許可。&lt;br /&gt;
 * 入力された場合は http:// または https:// のURLのみ許可する。&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    const fields = [&lt;br /&gt;
        &#039;official_site&#039;,&lt;br /&gt;
        &#039;official_x&#039;,&lt;br /&gt;
        &#039;official_instagram&#039;,&lt;br /&gt;
        &#039;official_facebook&#039;,&lt;br /&gt;
        &#039;official_youtube&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    fields.forEach(function (fieldName) {&lt;br /&gt;
        const input = document.querySelector(&lt;br /&gt;
            &#039;input[name=&amp;quot;Festival[&#039; + fieldName + &#039;]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (!input) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        input.inputMode = &#039;url&#039;;&lt;br /&gt;
&lt;br /&gt;
        const validateFestivalUrl = function () {&lt;br /&gt;
            const value = input.value.trim();&lt;br /&gt;
&lt;br /&gt;
            input.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (value === &#039;&#039;) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                const url = new URL(value);&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    url.protocol !== &#039;http:&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    url.protocol !== &#039;https:&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    input.setCustomValidity(&lt;br /&gt;
                        &#039;URLは http:// または https:// で始まるURLを入力してください。&#039;&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            } catch (e) {&lt;br /&gt;
                input.setCustomValidity(&lt;br /&gt;
                    &#039;正しいURL形式で入力してください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, validateFestivalUrl);&lt;br /&gt;
        input.addEventListener(&#039;change&#039;, validateFestivalUrl);&lt;br /&gt;
        input.addEventListener(&#039;invalid&#039;, validateFestivalUrl);&lt;br /&gt;
    });&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * FestivalType：slug形式チェック&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
$(function () {&lt;br /&gt;
    const slugInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalType[slug]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if (!slugInput) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    slugInput.spellcheck = false;&lt;br /&gt;
&lt;br /&gt;
    const validateFestivalTypeSlug = function () {&lt;br /&gt;
        const value = slugInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        slugInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 空欄の必須チェックは&lt;br /&gt;
         * Page Forms の mandatory に任せる。&lt;br /&gt;
         */&lt;br /&gt;
        if (value === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 英小文字・数字を基本とし、&lt;br /&gt;
         * 単語の区切りに半角ハイフンのみ許可する。&lt;br /&gt;
         *&lt;br /&gt;
         * 先頭・末尾のハイフン、&lt;br /&gt;
         * 連続ハイフンは許可しない。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)&lt;br /&gt;
        ) {&lt;br /&gt;
            slugInput.setCustomValidity(&lt;br /&gt;
                &#039;slugは英小文字・数字・半角ハイフンで入力してください。ハイフンは単語の区切りにのみ使用できます。&#039;&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    slugInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateFestivalTypeSlug&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    slugInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateFestivalTypeSlug&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    slugInput.addEventListener(&lt;br /&gt;
        &#039;invalid&#039;,&lt;br /&gt;
        validateFestivalTypeSlug&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    validateFestivalTypeSlug();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * FestivalType：&lt;br /&gt;
 * 上位分類の自己参照・循環参照チェック&lt;br /&gt;
 *&lt;br /&gt;
 * Page Forms が select 要素を差し替えても&lt;br /&gt;
 * 動作するようイベント委譲を使用する。&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
window.matsuriFestivalTypeParentValidationVersion = &#039;20260821-v2&#039;;&lt;br /&gt;
(function () {&lt;br /&gt;
    const currentTypeId = Number(&lt;br /&gt;
        mw.config.get(&#039;wgArticleId&#039;)&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    let requestId = 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeCargoValue(value) {&lt;br /&gt;
        return String(value).replace(&lt;br /&gt;
            /&#039;/g,&lt;br /&gt;
            &amp;quot;&#039;&#039;&amp;quot;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
        function getTypeByName(name) {&lt;br /&gt;
        return mw.loader.using(&lt;br /&gt;
            &#039;mediawiki.api&#039;&lt;br /&gt;
        ).then(function () {&lt;br /&gt;
            const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            return api.get({&lt;br /&gt;
                action: &#039;cargoquery&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                tables: &#039;FestivalTypes&#039;,&lt;br /&gt;
                fields:&lt;br /&gt;
                    &#039;type_id=type_id,&#039; +&lt;br /&gt;
                    &#039;name=name,&#039; +&lt;br /&gt;
                    &#039;parent_id=parent_id&#039;,&lt;br /&gt;
                where:&lt;br /&gt;
                    &amp;quot;name=&#039;&amp;quot; +&lt;br /&gt;
                    escapeCargoValue(name) +&lt;br /&gt;
                    &amp;quot;&#039;&amp;quot;,&lt;br /&gt;
                limit: &#039;2&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            const rows =&lt;br /&gt;
                Array.isArray(data.cargoquery)&lt;br /&gt;
                    ? data.cargoquery&lt;br /&gt;
                    : [];&lt;br /&gt;
&lt;br /&gt;
            return rows.map(function (item) {&lt;br /&gt;
                return item.title || item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTypeById(typeId) {&lt;br /&gt;
        return mw.loader.using(&lt;br /&gt;
            &#039;mediawiki.api&#039;&lt;br /&gt;
        ).then(function () {&lt;br /&gt;
            const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            return api.get({&lt;br /&gt;
                action: &#039;cargoquery&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                tables: &#039;FestivalTypes&#039;,&lt;br /&gt;
                fields:&lt;br /&gt;
                    &#039;type_id=type_id,&#039; +&lt;br /&gt;
                    &#039;name=name,&#039; +&lt;br /&gt;
                    &#039;parent_id=parent_id&#039;,&lt;br /&gt;
                where:&lt;br /&gt;
                    &#039;type_id=&#039; +&lt;br /&gt;
                    Number(typeId),&lt;br /&gt;
                limit: &#039;1&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            const rows =&lt;br /&gt;
                Array.isArray(data.cargoquery)&lt;br /&gt;
                    ? data.cargoquery&lt;br /&gt;
                    : [];&lt;br /&gt;
&lt;br /&gt;
            if (rows.length === 0) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return rows[0].title || rows[0];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateFestivalTypeParent(&lt;br /&gt;
        parentSelect&lt;br /&gt;
    ) {&lt;br /&gt;
        const thisRequest = ++requestId;&lt;br /&gt;
&lt;br /&gt;
        parentSelect.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        const parentName =&lt;br /&gt;
            parentSelect.value.trim();&lt;br /&gt;
&lt;br /&gt;
        const nameInput = document.querySelector(&lt;br /&gt;
            &#039;input[name=&amp;quot;FestivalType[name]&amp;quot;]&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        const currentName =&lt;br /&gt;
            nameInput&lt;br /&gt;
                ? nameInput.value.trim()&lt;br /&gt;
                : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 親なしは正常。&lt;br /&gt;
         */&lt;br /&gt;
        if (parentName === &#039;&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 分類名が同じならAPIを待たず&lt;br /&gt;
         * 即座に自己参照として拒否する。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            currentName !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            parentName === currentName&lt;br /&gt;
        ) {&lt;br /&gt;
            parentSelect.setCustomValidity(&lt;br /&gt;
                &#039;自分自身を上位分類に設定することはできません。&#039;&lt;br /&gt;
            );&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 新規ページはまだtype_idを持たないため、&lt;br /&gt;
         * 既存階層との循環は発生しない。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !Number.isInteger(currentTypeId) ||&lt;br /&gt;
            currentTypeId &amp;lt;= 0&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * API確認中はフォーム送信を止める。&lt;br /&gt;
         */&lt;br /&gt;
        parentSelect.setCustomValidity(&lt;br /&gt;
            &#039;上位分類を確認しています。&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 非同期処理中に別の選択へ変更された、&lt;br /&gt;
         * またはPage Formsがselectを差し替えたか確認する。&lt;br /&gt;
         */&lt;br /&gt;
        function isStaleRequest() {&lt;br /&gt;
            return (&lt;br /&gt;
                thisRequest !== requestId ||&lt;br /&gt;
                !document.contains(parentSelect)&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 選択した分類から親を順番に辿る。&lt;br /&gt;
         *&lt;br /&gt;
         * async / await は使用せず、&lt;br /&gt;
         * Promise の再帰処理で階層を確認する。&lt;br /&gt;
         */&lt;br /&gt;
        function walkParentChain(&lt;br /&gt;
            parentId,&lt;br /&gt;
            visited&lt;br /&gt;
        ) {&lt;br /&gt;
            if (&lt;br /&gt;
                parentId === undefined ||&lt;br /&gt;
                parentId === null ||&lt;br /&gt;
                String(parentId).trim() === &#039;&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                return Promise.resolve(true);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const numericParentId =&lt;br /&gt;
                Number(parentId);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 現在編集中の分類へ戻れば循環。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                numericParentId ===&lt;br /&gt;
                currentTypeId&lt;br /&gt;
            ) {&lt;br /&gt;
                parentSelect.setCustomValidity(&lt;br /&gt;
                    &#039;この上位分類を設定すると分類階層が循環するため選択できません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 既存データ側ですでに循環している場合。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                visited.has(&lt;br /&gt;
                    numericParentId&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                parentSelect.setCustomValidity(&lt;br /&gt;
                    &#039;選択した上位分類の階層に循環があります。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            visited.add(&lt;br /&gt;
                numericParentId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return getTypeById(&lt;br /&gt;
                numericParentId&lt;br /&gt;
            ).then(function (row) {&lt;br /&gt;
                if (isStaleRequest()) {&lt;br /&gt;
                    return false;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (!row) {&lt;br /&gt;
                    parentSelect.setCustomValidity(&lt;br /&gt;
                        &#039;上位分類の階層情報を確認できませんでした。&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    return false;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return walkParentChain(&lt;br /&gt;
                    row.parent_id,&lt;br /&gt;
                    visited&lt;br /&gt;
                );&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return getTypeByName(&lt;br /&gt;
            parentName&lt;br /&gt;
        ).then(function (parentRows) {&lt;br /&gt;
            /*&lt;br /&gt;
             * その間に別の選択へ変更された場合は&lt;br /&gt;
             * 古い結果を無視する。&lt;br /&gt;
             */&lt;br /&gt;
            if (isStaleRequest()) {&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (parentRows.length === 0) {&lt;br /&gt;
                parentSelect.setCustomValidity(&lt;br /&gt;
                    &#039;選択した上位分類を確認できませんでした。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 同名分類が複数ある場合は&lt;br /&gt;
             * parent_id を一意に決められない。&lt;br /&gt;
             */&lt;br /&gt;
            if (parentRows.length &amp;gt; 1) {&lt;br /&gt;
                parentSelect.setCustomValidity(&lt;br /&gt;
                    &#039;同じ名前の祭り分類が複数存在するため、上位分類を特定できません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const selectedParent =&lt;br /&gt;
                parentRows[0];&lt;br /&gt;
&lt;br /&gt;
            const selectedTypeId =&lt;br /&gt;
                Number(&lt;br /&gt;
                    selectedParent.type_id&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * IDでも自己参照をチェックする。&lt;br /&gt;
             * 分類名を編集中に変更した場合にも有効。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                selectedTypeId ===&lt;br /&gt;
                currentTypeId&lt;br /&gt;
            ) {&lt;br /&gt;
                parentSelect.setCustomValidity(&lt;br /&gt;
                    &#039;自分自身を上位分類に設定することはできません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const visited =&lt;br /&gt;
                new Set([&lt;br /&gt;
                    selectedTypeId&lt;br /&gt;
                ]);&lt;br /&gt;
&lt;br /&gt;
            return walkParentChain(&lt;br /&gt;
                selectedParent.parent_id,&lt;br /&gt;
                visited&lt;br /&gt;
            );&lt;br /&gt;
        }).then(function (isValid) {&lt;br /&gt;
            if (&lt;br /&gt;
                isValid === true &amp;amp;&amp;amp;&lt;br /&gt;
                !isStaleRequest()&lt;br /&gt;
            ) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * すべて正常。&lt;br /&gt;
                 */&lt;br /&gt;
                parentSelect.setCustomValidity(&#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return isValid;&lt;br /&gt;
        }, function (error) {&lt;br /&gt;
            if (isStaleRequest()) {&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            parentSelect.setCustomValidity(&lt;br /&gt;
                &#039;上位分類を確認できませんでした。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            console.error(&lt;br /&gt;
                &#039;祭り分類の上位分類チェックに失敗しました。&#039;,&lt;br /&gt;
                error&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return false;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Page Forms が select を差し替えても&lt;br /&gt;
     * document 側で変更を拾う。&lt;br /&gt;
     */&lt;br /&gt;
document.addEventListener(&lt;br /&gt;
    &#039;change&#039;,&lt;br /&gt;
    function (event) {&lt;br /&gt;
        const target =&lt;br /&gt;
            event.target;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            target &amp;amp;&amp;amp;&lt;br /&gt;
            target.matches(&lt;br /&gt;
                &#039;select[name=&amp;quot;FestivalType[parent_id]&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateFestivalTypeParent(&lt;br /&gt;
                target&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    true&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 初期表示時にも現在存在するselectを確認。&lt;br /&gt;
     */&lt;br /&gt;
    function validateCurrentParent() {&lt;br /&gt;
        const parentSelect =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;select[name=&amp;quot;FestivalType[parent_id]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (parentSelect) {&lt;br /&gt;
            validateFestivalTypeParent(&lt;br /&gt;
                parentSelect&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState ===&lt;br /&gt;
        &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            validateCurrentParent&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        validateCurrentParent();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Page Formsによる描画後にも再確認する。&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        mw.hook&lt;br /&gt;
    ) {&lt;br /&gt;
        mw.hook(&lt;br /&gt;
            &#039;wikipage.content&#039;&lt;br /&gt;
        ).add(&lt;br /&gt;
            validateCurrentParent&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
 * Area：&lt;br /&gt;
 * Area ID・上位地域・地域区分の整合性チェック&lt;br /&gt;
 *&lt;br /&gt;
 * 既存Areaデータの正式な階層ルール：&lt;br /&gt;
 *&lt;br /&gt;
 * prefecture&lt;br /&gt;
 *   parent_id = 0&lt;br /&gt;
 *&lt;br /&gt;
 * city / special_ward / town / village&lt;br /&gt;
 *   parent = prefecture&lt;br /&gt;
 *&lt;br /&gt;
 * ward&lt;br /&gt;
 *   parent = city&lt;br /&gt;
 *&lt;br /&gt;
 * 自己参照・存在しない親・循環参照も拒否する。&lt;br /&gt;
 *&lt;br /&gt;
 * MediaWiki 1.43対応のため&lt;br /&gt;
 * async / await は使用しない。&lt;br /&gt;
 * ========================================= */&lt;br /&gt;
&lt;br /&gt;
window.matsuriAreaParentValidationVersion = &#039;20260821-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    let requestId = 0;&lt;br /&gt;
&lt;br /&gt;
    function getAreaById(areaId) {&lt;br /&gt;
        return mw.loader.using(&lt;br /&gt;
            &#039;mediawiki.api&#039;&lt;br /&gt;
        ).then(function () {&lt;br /&gt;
            const api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            return api.get({&lt;br /&gt;
                action: &#039;cargoquery&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                tables: &#039;Areas&#039;,&lt;br /&gt;
                fields:&lt;br /&gt;
                    &#039;area_id=area_id,&#039; +&lt;br /&gt;
                    &#039;name=name,&#039; +&lt;br /&gt;
                    &#039;area_type=area_type,&#039; +&lt;br /&gt;
                    &#039;parent_id=parent_id&#039;,&lt;br /&gt;
                where:&lt;br /&gt;
                    &#039;area_id=&#039; +&lt;br /&gt;
                    Number(areaId),&lt;br /&gt;
                limit: &#039;2&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            const rows =&lt;br /&gt;
                Array.isArray(data.cargoquery)&lt;br /&gt;
                    ? data.cargoquery&lt;br /&gt;
                    : [];&lt;br /&gt;
&lt;br /&gt;
            return rows.map(function (item) {&lt;br /&gt;
                return item.title || item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getExpectedParentType(areaType) {&lt;br /&gt;
        switch (areaType) {&lt;br /&gt;
            case &#039;prefecture&#039;:&lt;br /&gt;
                return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;city&#039;:&lt;br /&gt;
            case &#039;special_ward&#039;:&lt;br /&gt;
            case &#039;town&#039;:&lt;br /&gt;
            case &#039;village&#039;:&lt;br /&gt;
                return &#039;prefecture&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;ward&#039;:&lt;br /&gt;
                return &#039;city&#039;;&lt;br /&gt;
&lt;br /&gt;
            default:&lt;br /&gt;
                return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getParentTypeMessage(areaType) {&lt;br /&gt;
        switch (areaType) {&lt;br /&gt;
            case &#039;city&#039;:&lt;br /&gt;
                return &#039;市の上位地域には都道府県を指定してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;special_ward&#039;:&lt;br /&gt;
                return &#039;特別区の上位地域には都道府県を指定してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;town&#039;:&lt;br /&gt;
                return &#039;町の上位地域には都道府県を指定してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;village&#039;:&lt;br /&gt;
                return &#039;村の上位地域には都道府県を指定してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
            case &#039;ward&#039;:&lt;br /&gt;
                return &#039;行政区の上位地域には市を指定してください。&#039;;&lt;br /&gt;
&lt;br /&gt;
            default:&lt;br /&gt;
                return &#039;地域区分と上位地域の組み合わせが正しくありません。&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function validateAreaParent() {&lt;br /&gt;
        const areaIdInput =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;input[name=&amp;quot;Area[area_id]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const areaTypeSelect =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;select[name=&amp;quot;Area[area_type]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const parentIdInput =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;input[name=&amp;quot;Area[parent_id]&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Areaフォーム以外では何もしない。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !areaIdInput ||&lt;br /&gt;
            !areaTypeSelect ||&lt;br /&gt;
            !parentIdInput&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const thisRequest = ++requestId;&lt;br /&gt;
&lt;br /&gt;
        areaIdInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
        parentIdInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        const areaIdText =&lt;br /&gt;
            areaIdInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        const parentIdText =&lt;br /&gt;
            parentIdInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        const areaType =&lt;br /&gt;
            areaTypeSelect.value;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Area ID は正の整数。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !/^[1-9][0-9]*$/.test(&lt;br /&gt;
                areaIdText&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            areaIdInput.setCustomValidity(&lt;br /&gt;
                &#039;Area IDは1以上の整数で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const areaId =&lt;br /&gt;
            Number(areaIdText);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * parent_id は0以上の整数。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            !/^(0|[1-9][0-9]*)$/.test(&lt;br /&gt;
                parentIdText&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            parentIdInput.setCustomValidity(&lt;br /&gt;
                &#039;上位地域IDは0以上の整数で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const parentId =&lt;br /&gt;
            Number(parentIdText);&lt;br /&gt;
&lt;br /&gt;
        const expectedParentType =&lt;br /&gt;
            getExpectedParentType(&lt;br /&gt;
                areaType&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 想定外のarea_type。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            expectedParentType === null&lt;br /&gt;
        ) {&lt;br /&gt;
            areaTypeSelect.setCustomValidity(&lt;br /&gt;
                &#039;地域区分を正しく選択してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        areaTypeSelect.setCustomValidity(&#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 都道府県は必ずROOT。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            areaType === &#039;prefecture&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            if (parentId !== 0) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    &#039;都道府県の上位地域IDは0にしてください。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 都道府県 parent_id=0 は正常。&lt;br /&gt;
             */&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 都道府県以外は必ず親を持つ。&lt;br /&gt;
         */&lt;br /&gt;
        if (parentId === 0) {&lt;br /&gt;
            parentIdInput.setCustomValidity(&lt;br /&gt;
                getParentTypeMessage(&lt;br /&gt;
                    areaType&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 自己参照。&lt;br /&gt;
         */&lt;br /&gt;
        if (parentId === areaId) {&lt;br /&gt;
            parentIdInput.setCustomValidity(&lt;br /&gt;
                &#039;自分自身を上位地域に設定することはできません。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * API確認中は保存を止める。&lt;br /&gt;
         */&lt;br /&gt;
        parentIdInput.setCustomValidity(&lt;br /&gt;
            &#039;上位地域を確認しています。&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        function isStaleRequest() {&lt;br /&gt;
            return (&lt;br /&gt;
                thisRequest !== requestId ||&lt;br /&gt;
                !document.contains(&lt;br /&gt;
                    parentIdInput&lt;br /&gt;
                )&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 親を順番に辿って循環参照を確認。&lt;br /&gt;
         */&lt;br /&gt;
        function walkParentChain(&lt;br /&gt;
            nextParentId,&lt;br /&gt;
            visited&lt;br /&gt;
        ) {&lt;br /&gt;
            if (&lt;br /&gt;
                nextParentId === undefined ||&lt;br /&gt;
                nextParentId === null ||&lt;br /&gt;
                String(nextParentId).trim() === &#039;&#039; ||&lt;br /&gt;
                Number(nextParentId) === 0&lt;br /&gt;
            ) {&lt;br /&gt;
                return Promise.resolve(true);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const numericParentId =&lt;br /&gt;
                Number(nextParentId);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 現在編集中のAreaへ戻れば循環。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                numericParentId === areaId&lt;br /&gt;
            ) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    &#039;この上位地域を設定すると地域階層が循環するため指定できません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 既存データ側ですでに循環している場合。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                visited.has(&lt;br /&gt;
                    numericParentId&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    &#039;選択した上位地域の階層に循環があります。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            visited.add(&lt;br /&gt;
                numericParentId&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return getAreaById(&lt;br /&gt;
                numericParentId&lt;br /&gt;
            ).then(function (rows) {&lt;br /&gt;
                if (isStaleRequest()) {&lt;br /&gt;
                    return false;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (rows.length !== 1) {&lt;br /&gt;
                    parentIdInput.setCustomValidity(&lt;br /&gt;
                        &#039;上位地域の階層情報を確認できませんでした。&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    return false;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return walkParentChain(&lt;br /&gt;
                    rows[0].parent_id,&lt;br /&gt;
                    visited&lt;br /&gt;
                );&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return getAreaById(&lt;br /&gt;
            parentId&lt;br /&gt;
        ).then(function (parentRows) {&lt;br /&gt;
            if (isStaleRequest()) {&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 存在しないparent_id。&lt;br /&gt;
             */&lt;br /&gt;
            if (parentRows.length === 0) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    &#039;指定した上位地域IDは存在しません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * area_id重複がDB側に存在する異常状態。&lt;br /&gt;
             */&lt;br /&gt;
            if (parentRows.length &amp;gt; 1) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    &#039;同じArea IDの地域が複数存在するため、上位地域を特定できません。&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const selectedParent =&lt;br /&gt;
                parentRows[0];&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * 地域区分と親地域区分の整合性。&lt;br /&gt;
             */&lt;br /&gt;
            if (&lt;br /&gt;
                selectedParent.area_type !==&lt;br /&gt;
                expectedParentType&lt;br /&gt;
            ) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&lt;br /&gt;
                    getParentTypeMessage(&lt;br /&gt;
                        areaType&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const visited =&lt;br /&gt;
                new Set([&lt;br /&gt;
                    parentId&lt;br /&gt;
                ]);&lt;br /&gt;
&lt;br /&gt;
            return walkParentChain(&lt;br /&gt;
                selectedParent.parent_id,&lt;br /&gt;
                visited&lt;br /&gt;
            );&lt;br /&gt;
        }).then(function (isValid) {&lt;br /&gt;
            if (&lt;br /&gt;
                isValid === true &amp;amp;&amp;amp;&lt;br /&gt;
                !isStaleRequest()&lt;br /&gt;
            ) {&lt;br /&gt;
                parentIdInput.setCustomValidity(&#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return isValid;&lt;br /&gt;
        }, function (error) {&lt;br /&gt;
            if (isStaleRequest()) {&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            parentIdInput.setCustomValidity(&lt;br /&gt;
                &#039;上位地域を確認できませんでした。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            console.error(&lt;br /&gt;
                &#039;Areaの上位地域チェックに失敗しました。&#039;,&lt;br /&gt;
                error&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            return false;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Page Forms上で値が変更された場合。&lt;br /&gt;
     */&lt;br /&gt;
    document.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        function (event) {&lt;br /&gt;
            const target =&lt;br /&gt;
                event.target;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                target &amp;amp;&amp;amp;&lt;br /&gt;
                (&lt;br /&gt;
                    target.matches(&lt;br /&gt;
                        &#039;input[name=&amp;quot;Area[area_id]&amp;quot;]&#039;&lt;br /&gt;
                    ) ||&lt;br /&gt;
                    target.matches(&lt;br /&gt;
                        &#039;select[name=&amp;quot;Area[area_type]&amp;quot;]&#039;&lt;br /&gt;
                    ) ||&lt;br /&gt;
                    target.matches(&lt;br /&gt;
                        &#039;input[name=&amp;quot;Area[parent_id]&amp;quot;]&#039;&lt;br /&gt;
                    )&lt;br /&gt;
                )&lt;br /&gt;
            ) {&lt;br /&gt;
                validateAreaParent();&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
        true&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 初期表示時の検証。&lt;br /&gt;
     */&lt;br /&gt;
    function validateCurrentArea() {&lt;br /&gt;
        if (&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;input[name=&amp;quot;Area[area_id]&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            validateAreaParent();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState === &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            validateCurrentArea&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        validateCurrentArea();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
/* ========================================&lt;br /&gt;
 * FestivalCalendar 開催年4桁チェック&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
$( function () {&lt;br /&gt;
&lt;br /&gt;
    const yearInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalCalendar[year]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if ( !yearInput ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 二重初期化防止&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        yearInput.dataset&lt;br /&gt;
            .festivalCalendarYearValidation === &#039;1&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    yearInput.dataset&lt;br /&gt;
        .festivalCalendarYearValidation = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
    yearInput.inputMode = &#039;numeric&#039;;&lt;br /&gt;
    yearInput.maxLength = 4;&lt;br /&gt;
&lt;br /&gt;
    const validateYear = function () {&lt;br /&gt;
&lt;br /&gt;
        const value =&lt;br /&gt;
            yearInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 空欄については&lt;br /&gt;
         * Page Forms の mandatory に任せる。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !/^\d{4}$/.test( value )&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            yearInput.setCustomValidity(&lt;br /&gt;
                &#039;開催年は4桁の数字で入力してください（例：2027）&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } else {&lt;br /&gt;
&lt;br /&gt;
            yearInput.setCustomValidity( &#039;&#039; );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    yearInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateYear&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    yearInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateYear&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 編集画面を開いた時点の値も検査&lt;br /&gt;
     */&lt;br /&gt;
    validateYear();&lt;br /&gt;
&lt;br /&gt;
    window&lt;br /&gt;
        .matsuriFestivalCalendarYearValidationVersion =&lt;br /&gt;
        &#039;20260821-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
/* ========================================&lt;br /&gt;
 * FestivalCalendar 開催日の前後関係チェック&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
$( function () {&lt;br /&gt;
&lt;br /&gt;
    const startInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalCalendar[start_date]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    const endInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalCalendar[end_date]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if ( !startInput || !endInput ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        endInput.dataset&lt;br /&gt;
            .festivalCalendarDateValidation === &#039;1&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    endInput.dataset&lt;br /&gt;
        .festivalCalendarDateValidation = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateDates = function () {&lt;br /&gt;
&lt;br /&gt;
        const startDate =&lt;br /&gt;
            startInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        const endDate =&lt;br /&gt;
            endInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 終了日は任意。&lt;br /&gt;
         * 両方入力されている場合だけ前後関係を確認する。&lt;br /&gt;
         *&lt;br /&gt;
         * type=date の値は YYYY-MM-DD なので&lt;br /&gt;
         * 文字列比較で日付順を判定できる。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            startDate !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            endDate !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            endDate &amp;lt; startDate&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            endInput.setCustomValidity(&lt;br /&gt;
                &#039;終了日は開始日以降の日付を入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } else {&lt;br /&gt;
&lt;br /&gt;
            endInput.setCustomValidity( &#039;&#039; );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    startInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateDates&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    startInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateDates&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    endInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateDates&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    endInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateDates&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    validateDates();&lt;br /&gt;
&lt;br /&gt;
    window&lt;br /&gt;
        .matsuriFestivalCalendarDateValidationVersion =&lt;br /&gt;
        &#039;20260821-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
/* ========================================&lt;br /&gt;
 * FestivalCalendar 予想来場者数チェック&lt;br /&gt;
 * ======================================== */&lt;br /&gt;
&lt;br /&gt;
$( function () {&lt;br /&gt;
&lt;br /&gt;
    const visitorsInput = document.querySelector(&lt;br /&gt;
        &#039;input[name=&amp;quot;FestivalCalendar[expected_visitors]&amp;quot;]&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    if ( !visitorsInput ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 二重初期化防止&lt;br /&gt;
     */&lt;br /&gt;
    if (&lt;br /&gt;
        visitorsInput.dataset&lt;br /&gt;
            .festivalCalendarVisitorsValidation === &#039;1&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    visitorsInput.dataset&lt;br /&gt;
        .festivalCalendarVisitorsValidation = &#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
    visitorsInput.inputMode = &#039;numeric&#039;;&lt;br /&gt;
&lt;br /&gt;
    const validateVisitors = function () {&lt;br /&gt;
&lt;br /&gt;
        const value =&lt;br /&gt;
            visitorsInput.value.trim();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 空欄は許可。&lt;br /&gt;
         * 入力する場合は0以上の整数のみ。&lt;br /&gt;
         */&lt;br /&gt;
        if (&lt;br /&gt;
            value !== &#039;&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            !/^\d+$/.test( value )&lt;br /&gt;
        ) {&lt;br /&gt;
&lt;br /&gt;
            visitorsInput.setCustomValidity(&lt;br /&gt;
                &#039;予想来場者数は0以上の整数で入力してください。&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } else {&lt;br /&gt;
&lt;br /&gt;
            visitorsInput.setCustomValidity( &#039;&#039; );&lt;br /&gt;
&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    visitorsInput.addEventListener(&lt;br /&gt;
        &#039;input&#039;,&lt;br /&gt;
        validateVisitors&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    visitorsInput.addEventListener(&lt;br /&gt;
        &#039;change&#039;,&lt;br /&gt;
        validateVisitors&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    validateVisitors();&lt;br /&gt;
&lt;br /&gt;
    window&lt;br /&gt;
        .matsuriFestivalCalendarVisitorsValidationVersion =&lt;br /&gt;
        &#039;20260821-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
} );&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * R9-3 MatsuriWiki privacy-safe photo uploader candidate.&lt;br /&gt;
 *&lt;br /&gt;
 * Candidate mode:&lt;br /&gt;
 *   REAL_UPLOAD_ENABLED = false&lt;br /&gt;
 *&lt;br /&gt;
 * No production upload can occur while this flag is false.&lt;br /&gt;
 */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    const REAL_UPLOAD_ENABLED = true;&lt;br /&gt;
&lt;br /&gt;
    const UI_ID =&lt;br /&gt;
        &#039;r9-photo-privacy-uploader&#039;;&lt;br /&gt;
&lt;br /&gt;
    const MAX_OUTPUT_BYTES =&lt;br /&gt;
        1536 * 1024;&lt;br /&gt;
&lt;br /&gt;
    const MAX_EDGE =&lt;br /&gt;
        1600;&lt;br /&gt;
&lt;br /&gt;
    const MIN_EDGE =&lt;br /&gt;
        720;&lt;br /&gt;
&lt;br /&gt;
    const MAIN_SELECTOR =&lt;br /&gt;
        &#039;[name=&amp;quot;FestivalStallPlacement[main_image]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const POSITION_SELECTOR =&lt;br /&gt;
        &#039;[name=&amp;quot;FestivalStallPlacement[position_status]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const LAT_SELECTOR =&lt;br /&gt;
        &#039;[name=&amp;quot;FestivalStallPlacement[latitude]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    const LON_SELECTOR =&lt;br /&gt;
        &#039;[name=&amp;quot;FestivalStallPlacement[longitude]&amp;quot;]&#039;;&lt;br /&gt;
&lt;br /&gt;
    function text(codePoints) {&lt;br /&gt;
        return codePoints&lt;br /&gt;
            .map(function (n) {&lt;br /&gt;
                return String.fromCodePoint(n);&lt;br /&gt;
            })&lt;br /&gt;
            .join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const LABELS = {&lt;br /&gt;
        title:&lt;br /&gt;
            &#039;\u5199\u771f\u3092\u5b89\u5168\u306b\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9&#039;,&lt;br /&gt;
&lt;br /&gt;
        help:&lt;br /&gt;
            &#039;\u5199\u771f\u306f\u30d6\u30e9\u30a6\u30b6\u5185\u3067\u753b\u50cf\u5316\u3057\u76f4\u3057\u3001GPS\u306a\u3069\u306eEXIF\u30e1\u30bf\u30c7\u30fc\u30bf\u3092\u9664\u53bb\u3057\u3066\u304b\u3089JPEG\u3068\u3057\u3066\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3057\u307e\u3059\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        select:&lt;br /&gt;
            &#039;\u5199\u771f\u3092\u9078\u629e&#039;,&lt;br /&gt;
&lt;br /&gt;
        publicName:&lt;br /&gt;
            &#039;\u516c\u958b\u30d5\u30a1\u30a4\u30eb\u540d&#039;,&lt;br /&gt;
&lt;br /&gt;
        gpsFound:&lt;br /&gt;
            &#039;GPS\u4f4d\u7f6e\u5019\u88dc\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002\u81ea\u52d5\u3067\u5ea7\u6a19\u306f\u5909\u66f4\u3057\u307e\u305b\u3093\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        gpsNotFound:&lt;br /&gt;
            &#039;GPS\u4f4d\u7f6e\u5019\u88dc\u306f\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        adoptGps:&lt;br /&gt;
            &#039;\u3053\u306e\u4f4d\u7f6e\u5019\u88dc\u3092\u4f7f\u7528&#039;,&lt;br /&gt;
&lt;br /&gt;
        upload:&lt;br /&gt;
            &#039;\u5b89\u5168\u306b\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9&#039;,&lt;br /&gt;
&lt;br /&gt;
        reset:&lt;br /&gt;
            &#039;\u9078\u629e\u3057\u305f\u5199\u771f\u3092\u30ea\u30bb\u30c3\u30c8&#039;,&lt;br /&gt;
&lt;br /&gt;
        processing:&lt;br /&gt;
            &#039;\u30d6\u30e9\u30a6\u30b6\u5185\u3067\u5b89\u5168\u51e6\u7406\u4e2d...&#039;,&lt;br /&gt;
&lt;br /&gt;
        ready:&lt;br /&gt;
            &#039;\u30b5\u30cb\u30bf\u30a4\u30ba\u6e08\u307fJPEG\u306e\u6e96\u5099\u304c\u3067\u304d\u307e\u3057\u305f\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        dryRun:&lt;br /&gt;
            &#039;R9-3 candidate\u306f\u30c9\u30e9\u30a4\u30e9\u30f3\u4e2d\u306e\u305f\u3081\u3001\u5b9f\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306f\u7121\u52b9\u3067\u3059\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        uploadSuccess:&lt;br /&gt;
            &#039;\u5b89\u5168\u306aJPEG\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u6210\u529f\u3057\u307e\u3057\u305f\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        uploadFailure:&lt;br /&gt;
            &#039;\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        existingFile:&lt;br /&gt;
            &#039;\u540c\u3058\u516c\u958b\u30d5\u30a1\u30a4\u30eb\u540d\u304c\u3059\u3067\u306b\u5b58\u5728\u3057\u307e\u3059\u3002\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u5909\u66f4\u3057\u3066\u304f\u3060\u3055\u3044\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        badType:&lt;br /&gt;
            &#039;JPG\u3001PNG\u3001WebP\u306e\u3044\u305a\u308c\u304b\u3092\u9078\u629e\u3057\u3066\u304f\u3060\u3055\u3044\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        badName:&lt;br /&gt;
            &#039;\u516c\u958b\u30d5\u30a1\u30a4\u30eb\u540d\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        gpsApplied:&lt;br /&gt;
            &#039;\u4f4d\u7f6e\u5019\u88dc\u3092\u7def\u5ea6\u30fb\u7d4c\u5ea6\u306b\u53cd\u6620\u3057\u307e\u3057\u305f\u3002\u4f4d\u7f6e\u78ba\u8a8d\u72b6\u614b\u306f\u5909\u66f4\u3057\u3066\u3044\u307e\u305b\u3093\u3002&#039;,&lt;br /&gt;
&lt;br /&gt;
        replaceCoords:&lt;br /&gt;
            &#039;\u3059\u3067\u306b\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u7def\u5ea6\u30fb\u7d4c\u5ea6\u3092\u3001\u5199\u771f\u306e\u4f4d\u7f6e\u5019\u88dc\u3067\u7f6e\u304d\u63db\u3048\u307e\u3059\u304b\uff1f&#039;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function dispatchInputChange(element) {&lt;br /&gt;
        element.dispatchEvent(&lt;br /&gt;
            new Event(&lt;br /&gt;
                &#039;input&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    bubbles: true&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        element.dispatchEvent(&lt;br /&gt;
            new Event(&lt;br /&gt;
                &#039;change&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    bubbles: true&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ascii(view, offset, count) {&lt;br /&gt;
        let out = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            offset &amp;lt; 0 ||&lt;br /&gt;
            count &amp;lt; 0 ||&lt;br /&gt;
            offset + count &amp;gt; view.byteLength&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (&lt;br /&gt;
            let i = 0;&lt;br /&gt;
            i &amp;lt; count;&lt;br /&gt;
            i++&lt;br /&gt;
        ) {&lt;br /&gt;
            const n =&lt;br /&gt;
                view.getUint8(&lt;br /&gt;
                    offset + i&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (n === 0) {&lt;br /&gt;
                break;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            out +=&lt;br /&gt;
                String.fromCharCode(n);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function detectImageType(buffer) {&lt;br /&gt;
        const view =&lt;br /&gt;
            new DataView(buffer);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            view.byteLength &amp;gt;= 3 &amp;amp;&amp;amp;&lt;br /&gt;
            view.getUint8(0) === 0xff &amp;amp;&amp;amp;&lt;br /&gt;
            view.getUint8(1) === 0xd8 &amp;amp;&amp;amp;&lt;br /&gt;
            view.getUint8(2) === 0xff&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;image/jpeg&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            view.byteLength &amp;gt;= 8 &amp;amp;&amp;amp;&lt;br /&gt;
            view.getUint32(0, false) ===&lt;br /&gt;
                0x89504e47 &amp;amp;&amp;amp;&lt;br /&gt;
            view.getUint32(4, false) ===&lt;br /&gt;
                0x0d0a1a0a&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;image/png&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            view.byteLength &amp;gt;= 12 &amp;amp;&amp;amp;&lt;br /&gt;
            ascii(&lt;br /&gt;
                view,&lt;br /&gt;
                0,&lt;br /&gt;
                4&lt;br /&gt;
            ) === &#039;RIFF&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            ascii(&lt;br /&gt;
                view,&lt;br /&gt;
                8,&lt;br /&gt;
                4&lt;br /&gt;
            ) === &#039;WEBP&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return &#039;image/webp&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function jpegExif(buffer) {&lt;br /&gt;
        const view =&lt;br /&gt;
            new DataView(buffer);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            view.byteLength &amp;lt; 4 ||&lt;br /&gt;
            view.getUint16(&lt;br /&gt;
                0,&lt;br /&gt;
                false&lt;br /&gt;
            ) !== 0xffd8&lt;br /&gt;
        ) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let p = 2;&lt;br /&gt;
&lt;br /&gt;
        while (&lt;br /&gt;
            p + 4 &amp;lt;=&lt;br /&gt;
            view.byteLength&lt;br /&gt;
        ) {&lt;br /&gt;
            if (&lt;br /&gt;
                view.getUint8(p) !==&lt;br /&gt;
                0xff&lt;br /&gt;
            ) {&lt;br /&gt;
                p++;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const marker =&lt;br /&gt;
                view.getUint8(&lt;br /&gt;
                    p + 1&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                marker === 0xda ||&lt;br /&gt;
                marker === 0xd9&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                marker &amp;gt;= 0xd0 &amp;amp;&amp;amp;&lt;br /&gt;
                marker &amp;lt;= 0xd7&lt;br /&gt;
            ) {&lt;br /&gt;
                p += 2;&lt;br /&gt;
                continue;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                p + 4 &amp;gt;&lt;br /&gt;
                view.byteLength&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const len =&lt;br /&gt;
                view.getUint16(&lt;br /&gt;
                    p + 2,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                len &amp;lt; 2 ||&lt;br /&gt;
                p + 2 + len &amp;gt;&lt;br /&gt;
                    view.byteLength&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                marker === 0xe1 &amp;amp;&amp;amp;&lt;br /&gt;
                ascii(&lt;br /&gt;
                    view,&lt;br /&gt;
                    p + 4,&lt;br /&gt;
                    6&lt;br /&gt;
                ) === &#039;Exif&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                return {&lt;br /&gt;
                    view:&lt;br /&gt;
                        view,&lt;br /&gt;
&lt;br /&gt;
                    tiff:&lt;br /&gt;
                        p + 10&lt;br /&gt;
                };&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            p +=&lt;br /&gt;
                2 + len;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function pngExif(buffer) {&lt;br /&gt;
        const view =&lt;br /&gt;
            new DataView(buffer);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            detectImageType(&lt;br /&gt;
                buffer&lt;br /&gt;
            ) !== &#039;image/png&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let p = 8;&lt;br /&gt;
&lt;br /&gt;
        while (&lt;br /&gt;
            p + 12 &amp;lt;=&lt;br /&gt;
            view.byteLength&lt;br /&gt;
        ) {&lt;br /&gt;
            const size =&lt;br /&gt;
                view.getUint32(&lt;br /&gt;
                    p,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const type =&lt;br /&gt;
                ascii(&lt;br /&gt;
                    view,&lt;br /&gt;
                    p + 4,&lt;br /&gt;
                    4&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const data =&lt;br /&gt;
                p + 8;&lt;br /&gt;
&lt;br /&gt;
            const end =&lt;br /&gt;
                data + size;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                end + 4 &amp;gt;&lt;br /&gt;
                view.byteLength&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                type === &#039;eXIf&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                let tiff =&lt;br /&gt;
                    data;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    size &amp;gt;= 6 &amp;amp;&amp;amp;&lt;br /&gt;
                    ascii(&lt;br /&gt;
                        view,&lt;br /&gt;
                        data,&lt;br /&gt;
                        6&lt;br /&gt;
                    ) === &#039;Exif&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    tiff += 6;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return {&lt;br /&gt;
                    view:&lt;br /&gt;
                        view,&lt;br /&gt;
&lt;br /&gt;
                    tiff:&lt;br /&gt;
                        tiff&lt;br /&gt;
                };&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            p =&lt;br /&gt;
                end + 4;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function webpExif(buffer) {&lt;br /&gt;
        const view =&lt;br /&gt;
            new DataView(buffer);&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            detectImageType(&lt;br /&gt;
                buffer&lt;br /&gt;
            ) !== &#039;image/webp&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        let p = 12;&lt;br /&gt;
&lt;br /&gt;
        while (&lt;br /&gt;
            p + 8 &amp;lt;=&lt;br /&gt;
            view.byteLength&lt;br /&gt;
        ) {&lt;br /&gt;
            const type =&lt;br /&gt;
                ascii(&lt;br /&gt;
                    view,&lt;br /&gt;
                    p,&lt;br /&gt;
                    4&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const size =&lt;br /&gt;
                view.getUint32(&lt;br /&gt;
                    p + 4,&lt;br /&gt;
                    true&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const data =&lt;br /&gt;
                p + 8;&lt;br /&gt;
&lt;br /&gt;
            const end =&lt;br /&gt;
                data + size;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                end &amp;gt;&lt;br /&gt;
                view.byteLength&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                type === &#039;EXIF&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                let tiff =&lt;br /&gt;
                    data;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    size &amp;gt;= 6 &amp;amp;&amp;amp;&lt;br /&gt;
                    ascii(&lt;br /&gt;
                        view,&lt;br /&gt;
                        data,&lt;br /&gt;
                        6&lt;br /&gt;
                    ) === &#039;Exif&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    tiff += 6;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                return {&lt;br /&gt;
                    view:&lt;br /&gt;
                        view,&lt;br /&gt;
&lt;br /&gt;
                    tiff:&lt;br /&gt;
                        tiff&lt;br /&gt;
                };&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            p =&lt;br /&gt;
                end +&lt;br /&gt;
                (&lt;br /&gt;
                    size % 2&lt;br /&gt;
                );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getExifContainer(&lt;br /&gt;
        buffer,&lt;br /&gt;
        imageType&lt;br /&gt;
    ) {&lt;br /&gt;
        if (&lt;br /&gt;
            imageType ===&lt;br /&gt;
            &#039;image/jpeg&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return jpegExif(&lt;br /&gt;
                buffer&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            imageType ===&lt;br /&gt;
            &#039;image/png&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return pngExif(&lt;br /&gt;
                buffer&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            imageType ===&lt;br /&gt;
            &#039;image/webp&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return webpExif(&lt;br /&gt;
                buffer&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parseGps(&lt;br /&gt;
        buffer,&lt;br /&gt;
        imageType&lt;br /&gt;
    ) {&lt;br /&gt;
        const found =&lt;br /&gt;
            getExifContainer(&lt;br /&gt;
                buffer,&lt;br /&gt;
                imageType&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!found) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            const view =&lt;br /&gt;
                found.view;&lt;br /&gt;
&lt;br /&gt;
            const tiff =&lt;br /&gt;
                found.tiff;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                tiff &amp;lt; 0 ||&lt;br /&gt;
                tiff + 8 &amp;gt;&lt;br /&gt;
                    view.byteLength&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const byteOrder =&lt;br /&gt;
                view.getUint16(&lt;br /&gt;
                    tiff,&lt;br /&gt;
                    false&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const littleEndian =&lt;br /&gt;
                byteOrder === 0x4949&lt;br /&gt;
                    ? true&lt;br /&gt;
                    : byteOrder === 0x4d4d&lt;br /&gt;
                        ? false&lt;br /&gt;
                        : null;&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                littleEndian ===&lt;br /&gt;
                null&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const u16 =&lt;br /&gt;
                function (offset) {&lt;br /&gt;
                    if (&lt;br /&gt;
                        offset &amp;lt; 0 ||&lt;br /&gt;
                        offset + 2 &amp;gt;&lt;br /&gt;
                            view.byteLength&lt;br /&gt;
                    ) {&lt;br /&gt;
                        throw new Error(&lt;br /&gt;
                            &#039;EXIF_BOUNDS&#039;&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return view.getUint16(&lt;br /&gt;
                        offset,&lt;br /&gt;
                        littleEndian&lt;br /&gt;
                    );&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
            const u32 =&lt;br /&gt;
                function (offset) {&lt;br /&gt;
                    if (&lt;br /&gt;
                        offset &amp;lt; 0 ||&lt;br /&gt;
                        offset + 4 &amp;gt;&lt;br /&gt;
                            view.byteLength&lt;br /&gt;
                    ) {&lt;br /&gt;
                        throw new Error(&lt;br /&gt;
                            &#039;EXIF_BOUNDS&#039;&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return view.getUint32(&lt;br /&gt;
                        offset,&lt;br /&gt;
                        littleEndian&lt;br /&gt;
                    );&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                u16(&lt;br /&gt;
                    tiff + 2&lt;br /&gt;
                ) !== 42&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const ifd0 =&lt;br /&gt;
                tiff +&lt;br /&gt;
                u32(&lt;br /&gt;
                    tiff + 4&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const ifd0Count =&lt;br /&gt;
                u16(ifd0);&lt;br /&gt;
&lt;br /&gt;
            let gpsOffset =&lt;br /&gt;
                null;&lt;br /&gt;
&lt;br /&gt;
            for (&lt;br /&gt;
                let i = 0;&lt;br /&gt;
                i &amp;lt; ifd0Count;&lt;br /&gt;
                i++&lt;br /&gt;
            ) {&lt;br /&gt;
                const entry =&lt;br /&gt;
                    ifd0 +&lt;br /&gt;
                    2 +&lt;br /&gt;
                    i * 12;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    entry + 12 &amp;gt;&lt;br /&gt;
                    view.byteLength&lt;br /&gt;
                ) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    u16(entry) ===&lt;br /&gt;
                    0x8825&lt;br /&gt;
                ) {&lt;br /&gt;
                    gpsOffset =&lt;br /&gt;
                        u32(&lt;br /&gt;
                            entry + 8&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                gpsOffset ===&lt;br /&gt;
                null&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const gpsIfd =&lt;br /&gt;
                tiff +&lt;br /&gt;
                gpsOffset;&lt;br /&gt;
&lt;br /&gt;
            const gpsCount =&lt;br /&gt;
                u16(gpsIfd);&lt;br /&gt;
&lt;br /&gt;
            let latRef = &#039;&#039;;&lt;br /&gt;
            let lonRef = &#039;&#039;;&lt;br /&gt;
            let latParts = null;&lt;br /&gt;
            let lonParts = null;&lt;br /&gt;
&lt;br /&gt;
            const rationalTriplet =&lt;br /&gt;
                function (offset) {&lt;br /&gt;
                    const values = [];&lt;br /&gt;
&lt;br /&gt;
                    for (&lt;br /&gt;
                        let i = 0;&lt;br /&gt;
                        i &amp;lt; 3;&lt;br /&gt;
                        i++&lt;br /&gt;
                    ) {&lt;br /&gt;
                        const numerator =&lt;br /&gt;
                            u32(&lt;br /&gt;
                                offset +&lt;br /&gt;
                                i * 8&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        const denominator =&lt;br /&gt;
                            u32(&lt;br /&gt;
                                offset +&lt;br /&gt;
                                i * 8 +&lt;br /&gt;
                                4&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            denominator ===&lt;br /&gt;
                            0&lt;br /&gt;
                        ) {&lt;br /&gt;
                            throw new Error(&lt;br /&gt;
                                &#039;EXIF_ZERO_DENOMINATOR&#039;&lt;br /&gt;
                            );&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        values.push(&lt;br /&gt;
                            numerator /&lt;br /&gt;
                            denominator&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    return values;&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
            for (&lt;br /&gt;
                let i = 0;&lt;br /&gt;
                i &amp;lt; gpsCount;&lt;br /&gt;
                i++&lt;br /&gt;
            ) {&lt;br /&gt;
                const entry =&lt;br /&gt;
                    gpsIfd +&lt;br /&gt;
                    2 +&lt;br /&gt;
                    i * 12;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    entry + 12 &amp;gt;&lt;br /&gt;
                    view.byteLength&lt;br /&gt;
                ) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const tag =&lt;br /&gt;
                    u16(entry);&lt;br /&gt;
&lt;br /&gt;
                const type =&lt;br /&gt;
                    u16(&lt;br /&gt;
                        entry + 2&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const count =&lt;br /&gt;
                    u32(&lt;br /&gt;
                        entry + 4&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const valueField =&lt;br /&gt;
                    entry + 8;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    (&lt;br /&gt;
                        tag === 1 ||&lt;br /&gt;
                        tag === 3&lt;br /&gt;
                    ) &amp;amp;&amp;amp;&lt;br /&gt;
                    type === 2&lt;br /&gt;
                ) {&lt;br /&gt;
                    const valueOffset =&lt;br /&gt;
                        count &amp;lt;= 4&lt;br /&gt;
                            ? valueField&lt;br /&gt;
                            : tiff +&lt;br /&gt;
                                u32(&lt;br /&gt;
                                    valueField&lt;br /&gt;
                                );&lt;br /&gt;
&lt;br /&gt;
                    const ref =&lt;br /&gt;
                        ascii(&lt;br /&gt;
                            view,&lt;br /&gt;
                            valueOffset,&lt;br /&gt;
                            count&lt;br /&gt;
                        )&lt;br /&gt;
                            .trim()&lt;br /&gt;
                            .toUpperCase();&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        tag === 1&lt;br /&gt;
                    ) {&lt;br /&gt;
                        latRef =&lt;br /&gt;
                            ref;&lt;br /&gt;
                    } else {&lt;br /&gt;
                        lonRef =&lt;br /&gt;
                            ref;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    (&lt;br /&gt;
                        tag === 2 ||&lt;br /&gt;
                        tag === 4&lt;br /&gt;
                    ) &amp;amp;&amp;amp;&lt;br /&gt;
                    type === 5 &amp;amp;&amp;amp;&lt;br /&gt;
                    count &amp;gt;= 3&lt;br /&gt;
                ) {&lt;br /&gt;
                    const dataOffset =&lt;br /&gt;
                        tiff +&lt;br /&gt;
                        u32(&lt;br /&gt;
                            valueField&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    const parts =&lt;br /&gt;
                        rationalTriplet(&lt;br /&gt;
                            dataOffset&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        tag === 2&lt;br /&gt;
                    ) {&lt;br /&gt;
                        latParts =&lt;br /&gt;
                            parts;&lt;br /&gt;
                    } else {&lt;br /&gt;
                        lonParts =&lt;br /&gt;
                            parts;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !latRef ||&lt;br /&gt;
                !lonRef ||&lt;br /&gt;
                !latParts ||&lt;br /&gt;
                !lonParts&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            const decimal =&lt;br /&gt;
                function (parts) {&lt;br /&gt;
                    return (&lt;br /&gt;
                        parts[0] +&lt;br /&gt;
                        parts[1] / 60 +&lt;br /&gt;
                        parts[2] / 3600&lt;br /&gt;
                    );&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
            let latitude =&lt;br /&gt;
                decimal(&lt;br /&gt;
                    latParts&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            let longitude =&lt;br /&gt;
                decimal(&lt;br /&gt;
                    lonParts&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                latRef ===&lt;br /&gt;
                &#039;S&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                latitude *= -1;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                lonRef ===&lt;br /&gt;
                &#039;W&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                longitude *= -1;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (&lt;br /&gt;
                !Number.isFinite(&lt;br /&gt;
                    latitude&lt;br /&gt;
                ) ||&lt;br /&gt;
                !Number.isFinite(&lt;br /&gt;
                    longitude&lt;br /&gt;
                ) ||&lt;br /&gt;
                latitude &amp;lt; -90 ||&lt;br /&gt;
                latitude &amp;gt; 90 ||&lt;br /&gt;
                longitude &amp;lt; -180 ||&lt;br /&gt;
                longitude &amp;gt; 180&lt;br /&gt;
            ) {&lt;br /&gt;
                return null;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                latitude:&lt;br /&gt;
                    latitude,&lt;br /&gt;
&lt;br /&gt;
                longitude:&lt;br /&gt;
                    longitude&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasToJpeg(&lt;br /&gt;
        canvas,&lt;br /&gt;
        quality&lt;br /&gt;
    ) {&lt;br /&gt;
        return new Promise(&lt;br /&gt;
            function (&lt;br /&gt;
                resolve,&lt;br /&gt;
                reject&lt;br /&gt;
            ) {&lt;br /&gt;
                canvas.toBlob(&lt;br /&gt;
                    function (blob) {&lt;br /&gt;
                        if (!blob) {&lt;br /&gt;
                            reject(&lt;br /&gt;
                                new Error(&lt;br /&gt;
                                    &#039;CANVAS_TO_BLOB_FAILED&#039;&lt;br /&gt;
                                )&lt;br /&gt;
                            );&lt;br /&gt;
&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        resolve(blob);&lt;br /&gt;
                    },&lt;br /&gt;
                    &#039;image/jpeg&#039;,&lt;br /&gt;
                    quality&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runAsyncGenerator(&lt;br /&gt;
        generator&lt;br /&gt;
    ) {&lt;br /&gt;
        return new Promise(&lt;br /&gt;
            function (&lt;br /&gt;
                resolve,&lt;br /&gt;
                reject&lt;br /&gt;
            ) {&lt;br /&gt;
                function step(&lt;br /&gt;
                    method,&lt;br /&gt;
                    value&lt;br /&gt;
                ) {&lt;br /&gt;
                    let result;&lt;br /&gt;
&lt;br /&gt;
                    try {&lt;br /&gt;
                        result =&lt;br /&gt;
                            generator[&lt;br /&gt;
                                method&lt;br /&gt;
                            ](&lt;br /&gt;
                                value&lt;br /&gt;
                            );&lt;br /&gt;
                    } catch (error) {&lt;br /&gt;
                        reject(&lt;br /&gt;
                            error&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        result.done&lt;br /&gt;
                    ) {&lt;br /&gt;
                        resolve(&lt;br /&gt;
                            result.value&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    Promise.resolve(&lt;br /&gt;
                        result.value&lt;br /&gt;
                    ).then(&lt;br /&gt;
                        function (&lt;br /&gt;
                            nextValue&lt;br /&gt;
                        ) {&lt;br /&gt;
                            step(&lt;br /&gt;
                                &#039;next&#039;,&lt;br /&gt;
                                nextValue&lt;br /&gt;
                            );&lt;br /&gt;
                        },&lt;br /&gt;
                        function (&lt;br /&gt;
                            error&lt;br /&gt;
                        ) {&lt;br /&gt;
                            step(&lt;br /&gt;
                                &#039;throw&#039;,&lt;br /&gt;
                                error&lt;br /&gt;
                            );&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                step(&lt;br /&gt;
                    &#039;next&#039;&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sanitizeImage(&lt;br /&gt;
        file&lt;br /&gt;
    ) {&lt;br /&gt;
        return runAsyncGenerator(&lt;br /&gt;
            (function* () {&lt;br /&gt;
        let bitmap = null;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            try {&lt;br /&gt;
                bitmap =&lt;br /&gt;
                    yield createImageBitmap(&lt;br /&gt;
                        file,&lt;br /&gt;
                        {&lt;br /&gt;
                            imageOrientation:&lt;br /&gt;
                                &#039;from-image&#039;&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
            } catch (_) {&lt;br /&gt;
                bitmap =&lt;br /&gt;
                    yield createImageBitmap(&lt;br /&gt;
                        file&lt;br /&gt;
                    );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            let width =&lt;br /&gt;
                bitmap.width;&lt;br /&gt;
&lt;br /&gt;
            let height =&lt;br /&gt;
                bitmap.height;&lt;br /&gt;
&lt;br /&gt;
            const initialScale =&lt;br /&gt;
                Math.min(&lt;br /&gt;
                    1,&lt;br /&gt;
                    MAX_EDGE /&lt;br /&gt;
                        Math.max(&lt;br /&gt;
                            width,&lt;br /&gt;
                            height&lt;br /&gt;
                        )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            width =&lt;br /&gt;
                Math.max(&lt;br /&gt;
                    1,&lt;br /&gt;
                    Math.round(&lt;br /&gt;
                        width *&lt;br /&gt;
                        initialScale&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            height =&lt;br /&gt;
                Math.max(&lt;br /&gt;
                    1,&lt;br /&gt;
                    Math.round(&lt;br /&gt;
                        height *&lt;br /&gt;
                        initialScale&lt;br /&gt;
                    )&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
            const qualities = [&lt;br /&gt;
                0.90,&lt;br /&gt;
                0.82,&lt;br /&gt;
                0.74,&lt;br /&gt;
                0.66,&lt;br /&gt;
                0.58&lt;br /&gt;
            ];&lt;br /&gt;
&lt;br /&gt;
            for (&lt;br /&gt;
                let resizePass = 0;&lt;br /&gt;
                resizePass &amp;lt; 6;&lt;br /&gt;
                resizePass++&lt;br /&gt;
            ) {&lt;br /&gt;
                const canvas =&lt;br /&gt;
                    document.createElement(&lt;br /&gt;
                        &#039;canvas&#039;&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                canvas.width =&lt;br /&gt;
                    width;&lt;br /&gt;
&lt;br /&gt;
                canvas.height =&lt;br /&gt;
                    height;&lt;br /&gt;
&lt;br /&gt;
                const context =&lt;br /&gt;
                    canvas.getContext(&lt;br /&gt;
                        &#039;2d&#039;,&lt;br /&gt;
                        {&lt;br /&gt;
                            alpha: false&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (!context) {&lt;br /&gt;
                    throw new Error(&lt;br /&gt;
                        &#039;CANVAS_CONTEXT_FAILED&#039;&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                context.fillStyle =&lt;br /&gt;
                    &#039;#fff&#039;;&lt;br /&gt;
&lt;br /&gt;
                context.fillRect(&lt;br /&gt;
                    0,&lt;br /&gt;
                    0,&lt;br /&gt;
                    width,&lt;br /&gt;
                    height&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                context.drawImage(&lt;br /&gt;
                    bitmap,&lt;br /&gt;
                    0,&lt;br /&gt;
                    0,&lt;br /&gt;
                    width,&lt;br /&gt;
                    height&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                for (&lt;br /&gt;
                    const quality&lt;br /&gt;
                    of qualities&lt;br /&gt;
                ) {&lt;br /&gt;
                    const blob =&lt;br /&gt;
                        yield canvasToJpeg(&lt;br /&gt;
                            canvas,&lt;br /&gt;
                            quality&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        blob.size &amp;lt;=&lt;br /&gt;
                        MAX_OUTPUT_BYTES&lt;br /&gt;
                    ) {&lt;br /&gt;
                        const safeBuffer =&lt;br /&gt;
                            yield blob&lt;br /&gt;
                                .arrayBuffer();&lt;br /&gt;
&lt;br /&gt;
                        if (&lt;br /&gt;
                            jpegExif(&lt;br /&gt;
                                safeBuffer&lt;br /&gt;
                            )&lt;br /&gt;
                        ) {&lt;br /&gt;
                            throw new Error(&lt;br /&gt;
                                &#039;EXIF_REMAINED_AFTER_SANITIZE&#039;&lt;br /&gt;
                            );&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        return {&lt;br /&gt;
                            blob:&lt;br /&gt;
                                blob,&lt;br /&gt;
&lt;br /&gt;
                            width:&lt;br /&gt;
                                width,&lt;br /&gt;
&lt;br /&gt;
                            height:&lt;br /&gt;
                                height,&lt;br /&gt;
&lt;br /&gt;
                            quality:&lt;br /&gt;
                                quality&lt;br /&gt;
                        };&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const nextWidth =&lt;br /&gt;
                    Math.round(&lt;br /&gt;
                        width * 0.85&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                const nextHeight =&lt;br /&gt;
                    Math.round(&lt;br /&gt;
                        height * 0.85&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    Math.max(&lt;br /&gt;
                        nextWidth,&lt;br /&gt;
                        nextHeight&lt;br /&gt;
                    ) &amp;lt;&lt;br /&gt;
                    MIN_EDGE&lt;br /&gt;
                ) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                width =&lt;br /&gt;
                    Math.max(&lt;br /&gt;
                        1,&lt;br /&gt;
                        nextWidth&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                height =&lt;br /&gt;
                    Math.max(&lt;br /&gt;
                        1,&lt;br /&gt;
                        nextHeight&lt;br /&gt;
                    );&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            throw new Error(&lt;br /&gt;
                &#039;SAFE_JPEG_SIZE_LIMIT_FAILED&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        } finally {&lt;br /&gt;
            if (&lt;br /&gt;
                bitmap &amp;amp;&amp;amp;&lt;br /&gt;
                typeof bitmap.close ===&lt;br /&gt;
                    &#039;function&#039;&lt;br /&gt;
            ) {&lt;br /&gt;
                bitmap.close();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
            }())&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function defaultFilename(&lt;br /&gt;
        originalName&lt;br /&gt;
    ) {&lt;br /&gt;
        let base =&lt;br /&gt;
            String(&lt;br /&gt;
                originalName ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            )&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /\.[^.]*$/,&lt;br /&gt;
                    &#039;&#039;&lt;br /&gt;
                )&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /[\\/:*?&amp;quot;&amp;lt;&amp;gt;|#\[\]{}]+/g,&lt;br /&gt;
                    &#039;-&#039;&lt;br /&gt;
                )&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /\s+/g,&lt;br /&gt;
                    &#039; &#039;&lt;br /&gt;
                )&lt;br /&gt;
                .trim();&lt;br /&gt;
&lt;br /&gt;
        if (!base) {&lt;br /&gt;
            base =&lt;br /&gt;
                &#039;festival-photo&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return (&lt;br /&gt;
            base +&lt;br /&gt;
            &#039;.jpg&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizedFilename(&lt;br /&gt;
        value&lt;br /&gt;
    ) {&lt;br /&gt;
        let name =&lt;br /&gt;
            String(&lt;br /&gt;
                value ||&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            )&lt;br /&gt;
                .trim()&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /^File:/i,&lt;br /&gt;
                    &#039;&#039;&lt;br /&gt;
                )&lt;br /&gt;
                .replace(&lt;br /&gt;
                    /^\u30d5\u30a1\u30a4\u30eb:/,&lt;br /&gt;
                    &#039;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
        if (!name) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        name =&lt;br /&gt;
            name.replace(&lt;br /&gt;
                /[\\/:*?&amp;quot;&amp;lt;&amp;gt;|#\[\]{}]+/g,&lt;br /&gt;
                &#039;-&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        name =&lt;br /&gt;
            name.replace(&lt;br /&gt;
                /\.[^.]*$/,&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        name =&lt;br /&gt;
            name.trim();&lt;br /&gt;
&lt;br /&gt;
        if (!name) {&lt;br /&gt;
            return &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return (&lt;br /&gt;
            name +&lt;br /&gt;
            &#039;.jpg&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeElement(&lt;br /&gt;
        tag,&lt;br /&gt;
        properties&lt;br /&gt;
    ) {&lt;br /&gt;
        const element =&lt;br /&gt;
            document.createElement(&lt;br /&gt;
                tag&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        Object.keys(&lt;br /&gt;
            properties || {}&lt;br /&gt;
        ).forEach(&lt;br /&gt;
            function (key) {&lt;br /&gt;
                if (&lt;br /&gt;
                    key ===&lt;br /&gt;
                    &#039;style&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    element.style.cssText =&lt;br /&gt;
                        properties[key];&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    key ===&lt;br /&gt;
                    &#039;textContent&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    element.textContent =&lt;br /&gt;
                        properties[key];&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                element[key] =&lt;br /&gt;
                    properties[key];&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return element;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initUploader() {&lt;br /&gt;
        const mainImage =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                MAIN_SELECTOR&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!mainImage) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                UI_ID&lt;br /&gt;
            )&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const positionStatus =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                POSITION_SELECTOR&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const latitudeInput =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                LAT_SELECTOR&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const longitudeInput =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                LON_SELECTOR&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            !positionStatus ||&lt;br /&gt;
            !latitudeInput ||&lt;br /&gt;
            !longitudeInput&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const uploadLink =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;.ext-pageforms-uploadable&#039; +&lt;br /&gt;
                &#039;[data-input-id=&amp;quot;&#039; +&lt;br /&gt;
                CSS.escape(&lt;br /&gt;
                    mainImage.id&lt;br /&gt;
                ) +&lt;br /&gt;
                &#039;&amp;quot;]&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        if (!uploadLink) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Privacy fail-closed:&lt;br /&gt;
         * once this workflow is detected,&lt;br /&gt;
         * hide the standard Page Forms&lt;br /&gt;
         * upload route for this field.&lt;br /&gt;
         */&lt;br /&gt;
        uploadLink.hidden =&lt;br /&gt;
            true;&lt;br /&gt;
&lt;br /&gt;
        uploadLink.setAttribute(&lt;br /&gt;
            &#039;aria-hidden&#039;,&lt;br /&gt;
            &#039;true&#039;&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        const originalState = {&lt;br /&gt;
            mainImage:&lt;br /&gt;
                mainImage.value,&lt;br /&gt;
&lt;br /&gt;
            positionStatus:&lt;br /&gt;
                positionStatus.value,&lt;br /&gt;
&lt;br /&gt;
            latitude:&lt;br /&gt;
                latitudeInput.value,&lt;br /&gt;
&lt;br /&gt;
            longitude:&lt;br /&gt;
                longitudeInput.value&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        const state = {&lt;br /&gt;
            sourceType:&lt;br /&gt;
                &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
            gps:&lt;br /&gt;
                null,&lt;br /&gt;
&lt;br /&gt;
            gpsFound:&lt;br /&gt;
                false,&lt;br /&gt;
&lt;br /&gt;
            gpsAdopted:&lt;br /&gt;
                false,&lt;br /&gt;
&lt;br /&gt;
            safeBlob:&lt;br /&gt;
                null,&lt;br /&gt;
&lt;br /&gt;
            safeWidth:&lt;br /&gt;
                0,&lt;br /&gt;
&lt;br /&gt;
            safeHeight:&lt;br /&gt;
                0,&lt;br /&gt;
&lt;br /&gt;
            safeQuality:&lt;br /&gt;
                0,&lt;br /&gt;
&lt;br /&gt;
            safeExifPresent:&lt;br /&gt;
                null,&lt;br /&gt;
&lt;br /&gt;
            objectUrl:&lt;br /&gt;
                &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
            uploadAttempted:&lt;br /&gt;
                false,&lt;br /&gt;
&lt;br /&gt;
            uploadPerformed:&lt;br /&gt;
                false,&lt;br /&gt;
&lt;br /&gt;
            uploadErrorCode:&lt;br /&gt;
                &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
            uploadedFilename:&lt;br /&gt;
                &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        const box =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;div&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    id:&lt;br /&gt;
                        UI_ID,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;margin-top:.75rem;&#039; +&lt;br /&gt;
                        &#039;padding:.85rem;&#039; +&lt;br /&gt;
                        &#039;border:1px solid #a2a9b1;&#039; +&lt;br /&gt;
                        &#039;border-radius:6px;&#039; +&lt;br /&gt;
                        &#039;background:#fff;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const heading =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;strong&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.title&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const help =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;div&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.help,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;margin:.4rem 0 .75rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const fileLabel =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;label&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.select,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;display:block;&#039; +&lt;br /&gt;
                        &#039;font-weight:600;&#039; +&lt;br /&gt;
                        &#039;margin-bottom:.25rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const fileInput =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;input&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    type:&lt;br /&gt;
                        &#039;file&#039;,&lt;br /&gt;
&lt;br /&gt;
                    accept:&lt;br /&gt;
                        &#039;image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const nameLabel =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;label&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.publicName,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;display:block;&#039; +&lt;br /&gt;
                        &#039;font-weight:600;&#039; +&lt;br /&gt;
                        &#039;margin-top:.75rem;&#039; +&lt;br /&gt;
                        &#039;margin-bottom:.25rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const filenameInput =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;input&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    type:&lt;br /&gt;
                        &#039;text&#039;,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;box-sizing:border-box;&#039; +&lt;br /&gt;
                        &#039;width:100%;&#039; +&lt;br /&gt;
                        &#039;max-width:32rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const status =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;div&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;margin-top:.65rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const info =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;div&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;margin-top:.35rem;&#039; +&lt;br /&gt;
                        &#039;font-size:.95em;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const preview =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;img&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    alt:&lt;br /&gt;
                        &#039;privacy-safe local preview&#039;,&lt;br /&gt;
&lt;br /&gt;
                    hidden:&lt;br /&gt;
                        true,&lt;br /&gt;
&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;display:none;&#039; +&lt;br /&gt;
                        &#039;max-width:260px;&#039; +&lt;br /&gt;
                        &#039;max-height:360px;&#039; +&lt;br /&gt;
                        &#039;object-fit:contain;&#039; +&lt;br /&gt;
                        &#039;margin-top:.65rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const actions =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;div&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    style:&lt;br /&gt;
                        &#039;display:flex;&#039; +&lt;br /&gt;
                        &#039;flex-wrap:wrap;&#039; +&lt;br /&gt;
                        &#039;gap:.5rem;&#039; +&lt;br /&gt;
                        &#039;margin-top:.75rem;&#039;&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const adoptButton =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;button&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    type:&lt;br /&gt;
                        &#039;button&#039;,&lt;br /&gt;
&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.adoptGps,&lt;br /&gt;
&lt;br /&gt;
                    hidden:&lt;br /&gt;
                        true&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const uploadButton =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;button&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    type:&lt;br /&gt;
                        &#039;button&#039;,&lt;br /&gt;
&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.upload,&lt;br /&gt;
&lt;br /&gt;
                    disabled:&lt;br /&gt;
                        true&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const resetButton =&lt;br /&gt;
            makeElement(&lt;br /&gt;
                &#039;button&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    type:&lt;br /&gt;
                        &#039;button&#039;,&lt;br /&gt;
&lt;br /&gt;
                    textContent:&lt;br /&gt;
                        LABELS.reset,&lt;br /&gt;
&lt;br /&gt;
                    disabled:&lt;br /&gt;
                        true&lt;br /&gt;
                }&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        fileLabel.appendChild(&lt;br /&gt;
            fileInput&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        actions.append(&lt;br /&gt;
            adoptButton,&lt;br /&gt;
            uploadButton,&lt;br /&gt;
            resetButton&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        box.append(&lt;br /&gt;
            heading,&lt;br /&gt;
            help,&lt;br /&gt;
            fileLabel,&lt;br /&gt;
            nameLabel,&lt;br /&gt;
            filenameInput,&lt;br /&gt;
            status,&lt;br /&gt;
            info,&lt;br /&gt;
            preview,&lt;br /&gt;
            actions&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        const previewWrapper =&lt;br /&gt;
            document.getElementById(&lt;br /&gt;
                mainImage.id +&lt;br /&gt;
                &#039;_imagepreview&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        (&lt;br /&gt;
            previewWrapper ||&lt;br /&gt;
            uploadLink&lt;br /&gt;
        ).insertAdjacentElement(&lt;br /&gt;
            &#039;afterend&#039;,&lt;br /&gt;
            box&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        function revokePreview() {&lt;br /&gt;
            if (&lt;br /&gt;
                state.objectUrl&lt;br /&gt;
            ) {&lt;br /&gt;
                URL.revokeObjectURL(&lt;br /&gt;
                    state.objectUrl&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                state.objectUrl =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resetSelection() {&lt;br /&gt;
            revokePreview();&lt;br /&gt;
&lt;br /&gt;
            fileInput.value =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            filenameInput.value =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            preview.removeAttribute(&lt;br /&gt;
                &#039;src&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
            preview.hidden =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            preview.style.display =&lt;br /&gt;
                &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
            adoptButton.hidden =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            uploadButton.disabled =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            resetButton.disabled =&lt;br /&gt;
                true;&lt;br /&gt;
&lt;br /&gt;
            state.sourceType =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            state.gps =&lt;br /&gt;
                null;&lt;br /&gt;
&lt;br /&gt;
            state.gpsFound =&lt;br /&gt;
                false;&lt;br /&gt;
&lt;br /&gt;
            state.gpsAdopted =&lt;br /&gt;
                false;&lt;br /&gt;
&lt;br /&gt;
            state.safeBlob =&lt;br /&gt;
                null;&lt;br /&gt;
&lt;br /&gt;
            state.safeWidth =&lt;br /&gt;
                0;&lt;br /&gt;
&lt;br /&gt;
            state.safeHeight =&lt;br /&gt;
                0;&lt;br /&gt;
&lt;br /&gt;
            state.safeQuality =&lt;br /&gt;
                0;&lt;br /&gt;
&lt;br /&gt;
            state.safeExifPresent =&lt;br /&gt;
                null;&lt;br /&gt;
&lt;br /&gt;
            state.uploadAttempted =&lt;br /&gt;
                false;&lt;br /&gt;
&lt;br /&gt;
            state.uploadPerformed =&lt;br /&gt;
                false;&lt;br /&gt;
&lt;br /&gt;
            state.uploadErrorCode =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            state.uploadedFilename =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            status.textContent =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            info.textContent =&lt;br /&gt;
                &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function safeStateReport() {&lt;br /&gt;
            return {&lt;br /&gt;
                realUploadEnabled:&lt;br /&gt;
                    REAL_UPLOAD_ENABLED,&lt;br /&gt;
&lt;br /&gt;
                sourceType:&lt;br /&gt;
                    state.sourceType,&lt;br /&gt;
&lt;br /&gt;
                gpsFound:&lt;br /&gt;
                    state.gpsFound,&lt;br /&gt;
&lt;br /&gt;
                gpsAdopted:&lt;br /&gt;
                    state.gpsAdopted,&lt;br /&gt;
&lt;br /&gt;
                gpsValuesPrinted:&lt;br /&gt;
                    false,&lt;br /&gt;
&lt;br /&gt;
                sanitizedReady:&lt;br /&gt;
                    !!state.safeBlob,&lt;br /&gt;
&lt;br /&gt;
                sanitizedType:&lt;br /&gt;
                    state.safeBlob&lt;br /&gt;
                        ? state.safeBlob.type&lt;br /&gt;
                        : &#039;&#039;,&lt;br /&gt;
&lt;br /&gt;
                sanitizedSize:&lt;br /&gt;
                    state.safeBlob&lt;br /&gt;
                        ? state.safeBlob.size&lt;br /&gt;
                        : 0,&lt;br /&gt;
&lt;br /&gt;
                sanitizedWidth:&lt;br /&gt;
                    state.safeWidth,&lt;br /&gt;
&lt;br /&gt;
                sanitizedHeight:&lt;br /&gt;
                    state.safeHeight,&lt;br /&gt;
&lt;br /&gt;
                sanitizedExifApp1Found:&lt;br /&gt;
                    state.safeExifPresent,&lt;br /&gt;
&lt;br /&gt;
                outputWithin1536KiB:&lt;br /&gt;
                    !!state.safeBlob &amp;amp;&amp;amp;&lt;br /&gt;
                    state.safeBlob.size &amp;lt;=&lt;br /&gt;
                        MAX_OUTPUT_BYTES,&lt;br /&gt;
&lt;br /&gt;
                publicFilename:&lt;br /&gt;
                    normalizedFilename(&lt;br /&gt;
                        filenameInput.value&lt;br /&gt;
                    ),&lt;br /&gt;
&lt;br /&gt;
                mainImageChanged:&lt;br /&gt;
                    mainImage.value !==&lt;br /&gt;
                    originalState.mainImage,&lt;br /&gt;
&lt;br /&gt;
                positionStatusChanged:&lt;br /&gt;
                    positionStatus.value !==&lt;br /&gt;
                    originalState.positionStatus,&lt;br /&gt;
&lt;br /&gt;
                latitudeHasValue:&lt;br /&gt;
                    !!latitudeInput.value&lt;br /&gt;
                        .trim(),&lt;br /&gt;
&lt;br /&gt;
                longitudeHasValue:&lt;br /&gt;
                    !!longitudeInput.value&lt;br /&gt;
                        .trim(),&lt;br /&gt;
&lt;br /&gt;
                uploadAttempted:&lt;br /&gt;
                    state.uploadAttempted,&lt;br /&gt;
&lt;br /&gt;
                uploadPerformed:&lt;br /&gt;
                    state.uploadPerformed,&lt;br /&gt;
&lt;br /&gt;
                uploadErrorCode:&lt;br /&gt;
                    state.uploadErrorCode,&lt;br /&gt;
&lt;br /&gt;
                uploadedFilename:&lt;br /&gt;
                    state.uploadedFilename,&lt;br /&gt;
&lt;br /&gt;
                standardUploadHidden:&lt;br /&gt;
                    uploadLink.hidden ===&lt;br /&gt;
                    true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        window&lt;br /&gt;
            .__r9PhotoPrivacyUploaderState =&lt;br /&gt;
            safeStateReport;&lt;br /&gt;
&lt;br /&gt;
        window&lt;br /&gt;
            .__r9PhotoPrivacyUploaderCleanup =&lt;br /&gt;
            function () {&lt;br /&gt;
                revokePreview();&lt;br /&gt;
&lt;br /&gt;
                mainImage.value =&lt;br /&gt;
                    originalState.mainImage;&lt;br /&gt;
&lt;br /&gt;
                positionStatus.value =&lt;br /&gt;
                    originalState.positionStatus;&lt;br /&gt;
&lt;br /&gt;
                latitudeInput.value =&lt;br /&gt;
                    originalState.latitude;&lt;br /&gt;
&lt;br /&gt;
                longitudeInput.value =&lt;br /&gt;
                    originalState.longitude;&lt;br /&gt;
&lt;br /&gt;
                box.remove();&lt;br /&gt;
&lt;br /&gt;
                uploadLink.hidden =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                uploadLink.removeAttribute(&lt;br /&gt;
                    &#039;aria-hidden&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                delete window&lt;br /&gt;
                    .__r9PhotoPrivacyUploaderState;&lt;br /&gt;
&lt;br /&gt;
                delete window&lt;br /&gt;
                    .__r9PhotoPrivacyUploaderCleanup;&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
        fileInput.addEventListener(&lt;br /&gt;
            &#039;change&#039;,&lt;br /&gt;
            function () {&lt;br /&gt;
                return runAsyncGenerator(&lt;br /&gt;
                    (function* () {&lt;br /&gt;
                const file =&lt;br /&gt;
                    fileInput.files &amp;amp;&amp;amp;&lt;br /&gt;
                    fileInput.files[0];&lt;br /&gt;
&lt;br /&gt;
                if (!file) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                revokePreview();&lt;br /&gt;
&lt;br /&gt;
                state.gps =&lt;br /&gt;
                    null;&lt;br /&gt;
&lt;br /&gt;
                state.gpsFound =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                state.gpsAdopted =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                state.safeBlob =&lt;br /&gt;
                    null;&lt;br /&gt;
&lt;br /&gt;
                state.safeExifPresent =&lt;br /&gt;
                    null;&lt;br /&gt;
&lt;br /&gt;
                state.uploadAttempted =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                state.uploadPerformed =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                state.uploadErrorCode =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                state.uploadedFilename =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                uploadButton.disabled =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                resetButton.disabled =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                adoptButton.hidden =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                preview.hidden =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                preview.style.display =&lt;br /&gt;
                    &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
                status.textContent =&lt;br /&gt;
                    LABELS.processing;&lt;br /&gt;
&lt;br /&gt;
                info.textContent =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                try {&lt;br /&gt;
                    const originalBuffer =&lt;br /&gt;
                        yield file&lt;br /&gt;
                            .arrayBuffer();&lt;br /&gt;
&lt;br /&gt;
                    const imageType =&lt;br /&gt;
                        detectImageType(&lt;br /&gt;
                            originalBuffer&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        ![&lt;br /&gt;
                            &#039;image/jpeg&#039;,&lt;br /&gt;
                            &#039;image/png&#039;,&lt;br /&gt;
                            &#039;image/webp&#039;&lt;br /&gt;
                        ].includes(&lt;br /&gt;
                            imageType&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        throw new Error(&lt;br /&gt;
                            &#039;UNSUPPORTED_IMAGE_TYPE&#039;&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    state.sourceType =&lt;br /&gt;
                        imageType;&lt;br /&gt;
&lt;br /&gt;
                    state.gps =&lt;br /&gt;
                        parseGps(&lt;br /&gt;
                            originalBuffer,&lt;br /&gt;
                            imageType&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    state.gpsFound =&lt;br /&gt;
                        !!state.gps;&lt;br /&gt;
&lt;br /&gt;
                    const safe =&lt;br /&gt;
                        yield sanitizeImage(&lt;br /&gt;
                            file&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    state.safeBlob =&lt;br /&gt;
                        safe.blob;&lt;br /&gt;
&lt;br /&gt;
                    state.safeWidth =&lt;br /&gt;
                        safe.width;&lt;br /&gt;
&lt;br /&gt;
                    state.safeHeight =&lt;br /&gt;
                        safe.height;&lt;br /&gt;
&lt;br /&gt;
                    state.safeQuality =&lt;br /&gt;
                        safe.quality;&lt;br /&gt;
&lt;br /&gt;
                    const safeBuffer =&lt;br /&gt;
                        yield safe.blob&lt;br /&gt;
                            .arrayBuffer();&lt;br /&gt;
&lt;br /&gt;
                    state.safeExifPresent =&lt;br /&gt;
                        !!jpegExif(&lt;br /&gt;
                            safeBuffer&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        state.safeExifPresent&lt;br /&gt;
                    ) {&lt;br /&gt;
                        throw new Error(&lt;br /&gt;
                            &#039;SANITIZED_JPEG_HAS_EXIF&#039;&lt;br /&gt;
                        );&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    filenameInput.value =&lt;br /&gt;
                        defaultFilename(&lt;br /&gt;
                            file.name&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    state.objectUrl =&lt;br /&gt;
                        URL.createObjectURL(&lt;br /&gt;
                            safe.blob&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    preview.src =&lt;br /&gt;
                        state.objectUrl;&lt;br /&gt;
&lt;br /&gt;
                    preview.hidden =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    preview.style.display =&lt;br /&gt;
                        &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
                    adoptButton.hidden =&lt;br /&gt;
                        !state.gpsFound;&lt;br /&gt;
&lt;br /&gt;
                    uploadButton.disabled =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    status.textContent =&lt;br /&gt;
                        state.gpsFound&lt;br /&gt;
                            ? LABELS.gpsFound&lt;br /&gt;
                            : LABELS.gpsNotFound;&lt;br /&gt;
&lt;br /&gt;
                    info.textContent =&lt;br /&gt;
                        LABELS.ready +&lt;br /&gt;
                        &#039; &#039; +&lt;br /&gt;
                        safe.width +&lt;br /&gt;
                        &#039; x &#039; +&lt;br /&gt;
                        safe.height +&lt;br /&gt;
                        &#039; / &#039; +&lt;br /&gt;
                        Math.ceil(&lt;br /&gt;
                            safe.blob.size /&lt;br /&gt;
                            1024&lt;br /&gt;
                        ) +&lt;br /&gt;
                        &#039; KiB&#039;;&lt;br /&gt;
&lt;br /&gt;
                } catch (error) {&lt;br /&gt;
                    state.safeBlob =&lt;br /&gt;
                        null;&lt;br /&gt;
&lt;br /&gt;
                    uploadButton.disabled =&lt;br /&gt;
                        true;&lt;br /&gt;
&lt;br /&gt;
                    adoptButton.hidden =&lt;br /&gt;
                        true;&lt;br /&gt;
&lt;br /&gt;
                    preview.hidden =&lt;br /&gt;
                        true;&lt;br /&gt;
&lt;br /&gt;
                    preview.style.display =&lt;br /&gt;
                        &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        error &amp;amp;&amp;amp;&lt;br /&gt;
                        error.message ===&lt;br /&gt;
                            &#039;UNSUPPORTED_IMAGE_TYPE&#039;&lt;br /&gt;
                    ) {&lt;br /&gt;
                        status.textContent =&lt;br /&gt;
                            LABELS.badType;&lt;br /&gt;
                    } else {&lt;br /&gt;
                        status.textContent =&lt;br /&gt;
                            &#039;R9 local processing error: &#039; +&lt;br /&gt;
                            (&lt;br /&gt;
                                error &amp;amp;&amp;amp;&lt;br /&gt;
                                error.message&lt;br /&gt;
                                    ? error.message&lt;br /&gt;
                                    : &#039;UNKNOWN&#039;&lt;br /&gt;
                            );&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                    }())&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        adoptButton.addEventListener(&lt;br /&gt;
            &#039;click&#039;,&lt;br /&gt;
            function () {&lt;br /&gt;
                if (!state.gps) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const existingCoordinates =&lt;br /&gt;
                    !!latitudeInput.value&lt;br /&gt;
                        .trim() ||&lt;br /&gt;
                    !!longitudeInput.value&lt;br /&gt;
                        .trim();&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    existingCoordinates &amp;amp;&amp;amp;&lt;br /&gt;
                    !window.confirm(&lt;br /&gt;
                        LABELS.replaceCoords&lt;br /&gt;
                    )&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                latitudeInput.value =&lt;br /&gt;
                    state.gps&lt;br /&gt;
                        .latitude&lt;br /&gt;
                        .toFixed(8);&lt;br /&gt;
&lt;br /&gt;
                longitudeInput.value =&lt;br /&gt;
                    state.gps&lt;br /&gt;
                        .longitude&lt;br /&gt;
                        .toFixed(8);&lt;br /&gt;
&lt;br /&gt;
                dispatchInputChange(&lt;br /&gt;
                    latitudeInput&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                dispatchInputChange(&lt;br /&gt;
                    longitudeInput&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                state.gpsAdopted =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                status.textContent =&lt;br /&gt;
                    LABELS.gpsApplied;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        resetButton.addEventListener(&lt;br /&gt;
            &#039;click&#039;,&lt;br /&gt;
            function () {&lt;br /&gt;
                resetSelection();&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        uploadButton.addEventListener(&lt;br /&gt;
            &#039;click&#039;,&lt;br /&gt;
            function () {&lt;br /&gt;
                return runAsyncGenerator(&lt;br /&gt;
                    (function* () {&lt;br /&gt;
                if (!state.safeBlob) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    !REAL_UPLOAD_ENABLED&lt;br /&gt;
                ) {&lt;br /&gt;
                    state.uploadAttempted =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    state.uploadPerformed =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    status.textContent =&lt;br /&gt;
                        LABELS.dryRun;&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                const filename =&lt;br /&gt;
                    normalizedFilename(&lt;br /&gt;
                        filenameInput.value&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                if (!filename) {&lt;br /&gt;
                    status.textContent =&lt;br /&gt;
                        LABELS.badName;&lt;br /&gt;
&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                state.uploadAttempted =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                state.uploadPerformed =&lt;br /&gt;
                    false;&lt;br /&gt;
&lt;br /&gt;
                state.uploadErrorCode =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                state.uploadedFilename =&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                uploadButton.disabled =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                fileInput.disabled =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                filenameInput.disabled =&lt;br /&gt;
                    true;&lt;br /&gt;
&lt;br /&gt;
                try {&lt;br /&gt;
                    const api =&lt;br /&gt;
                        new mw.Api();&lt;br /&gt;
&lt;br /&gt;
                    const query =&lt;br /&gt;
                        yield api.get({&lt;br /&gt;
                            action:&lt;br /&gt;
                                &#039;query&#039;,&lt;br /&gt;
&lt;br /&gt;
                            titles:&lt;br /&gt;
                                &#039;File:&#039; +&lt;br /&gt;
                                filename,&lt;br /&gt;
&lt;br /&gt;
                            prop:&lt;br /&gt;
                                &#039;imageinfo&#039;,&lt;br /&gt;
&lt;br /&gt;
                            iiprop:&lt;br /&gt;
                                &#039;url|size|sha1&#039;,&lt;br /&gt;
&lt;br /&gt;
                            formatversion:&lt;br /&gt;
                                2&lt;br /&gt;
                        });&lt;br /&gt;
&lt;br /&gt;
                    const page =&lt;br /&gt;
                        query &amp;amp;&amp;amp;&lt;br /&gt;
                        query.query &amp;amp;&amp;amp;&lt;br /&gt;
                        query.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                        query.query.pages[0]&lt;br /&gt;
                            ? query.query.pages[0]&lt;br /&gt;
                            : {};&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page.missing !==&lt;br /&gt;
                        true&lt;br /&gt;
                    ) {&lt;br /&gt;
                        state.uploadErrorCode =&lt;br /&gt;
                            &#039;FILE_ALREADY_EXISTS&#039;;&lt;br /&gt;
&lt;br /&gt;
                        status.textContent =&lt;br /&gt;
                            LABELS.existingFile;&lt;br /&gt;
&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    const result =&lt;br /&gt;
                        yield new Promise(&lt;br /&gt;
                            function (&lt;br /&gt;
                                resolve,&lt;br /&gt;
                                reject&lt;br /&gt;
                            ) {&lt;br /&gt;
                                api.upload(&lt;br /&gt;
                                    state.safeBlob,&lt;br /&gt;
                                    {&lt;br /&gt;
                                        filename:&lt;br /&gt;
                                            filename,&lt;br /&gt;
&lt;br /&gt;
                                        comment:&lt;br /&gt;
                                            &#039;Uploaded via MatsuriWiki privacy-safe photo uploader&#039;&lt;br /&gt;
                                    }&lt;br /&gt;
                                )&lt;br /&gt;
                                    .done(&lt;br /&gt;
                                        function (&lt;br /&gt;
                                            data&lt;br /&gt;
                                        ) {&lt;br /&gt;
                                            resolve(&lt;br /&gt;
                                                data&lt;br /&gt;
                                            );&lt;br /&gt;
                                        }&lt;br /&gt;
                                    )&lt;br /&gt;
                                    .fail(&lt;br /&gt;
                                        function (&lt;br /&gt;
                                            code,&lt;br /&gt;
                                            data&lt;br /&gt;
                                        ) {&lt;br /&gt;
                                            reject({&lt;br /&gt;
                                                code:&lt;br /&gt;
                                                    code,&lt;br /&gt;
&lt;br /&gt;
                                                data:&lt;br /&gt;
                                                    data&lt;br /&gt;
                                            });&lt;br /&gt;
                                        }&lt;br /&gt;
                                    );&lt;br /&gt;
                            }&lt;br /&gt;
                        );&lt;br /&gt;
&lt;br /&gt;
                    const upload =&lt;br /&gt;
                        result &amp;amp;&amp;amp;&lt;br /&gt;
                        result.upload&lt;br /&gt;
                            ? result.upload&lt;br /&gt;
                            : null;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        !upload ||&lt;br /&gt;
                        upload.result !==&lt;br /&gt;
                            &#039;Success&#039;&lt;br /&gt;
                    ) {&lt;br /&gt;
                        throw {&lt;br /&gt;
                            code:&lt;br /&gt;
                                &#039;UPLOAD_NOT_SUCCESS&#039;,&lt;br /&gt;
&lt;br /&gt;
                            data:&lt;br /&gt;
                                result&lt;br /&gt;
                        };&lt;br /&gt;
                    }&lt;br /&gt;
&lt;br /&gt;
                    const uploadedFilename =&lt;br /&gt;
                        upload.filename ||&lt;br /&gt;
                        filename;&lt;br /&gt;
&lt;br /&gt;
                    /*&lt;br /&gt;
                     * This is intentionally the&lt;br /&gt;
                     * only main_image write path.&lt;br /&gt;
                     * It is reached only after&lt;br /&gt;
                     * MediaWiki reports upload&lt;br /&gt;
                     * success.&lt;br /&gt;
                     */&lt;br /&gt;
                    mainImage.value =&lt;br /&gt;
                        uploadedFilename;&lt;br /&gt;
&lt;br /&gt;
                    dispatchInputChange(&lt;br /&gt;
                        mainImage&lt;br /&gt;
                    );&lt;br /&gt;
&lt;br /&gt;
                    state.uploadPerformed =&lt;br /&gt;
                        true;&lt;br /&gt;
&lt;br /&gt;
                    state.uploadedFilename =&lt;br /&gt;
                        uploadedFilename;&lt;br /&gt;
&lt;br /&gt;
                    status.textContent =&lt;br /&gt;
                        LABELS.uploadSuccess;&lt;br /&gt;
&lt;br /&gt;
                } catch (error) {&lt;br /&gt;
                    state.uploadPerformed =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    state.uploadErrorCode =&lt;br /&gt;
                        error &amp;amp;&amp;amp;&lt;br /&gt;
                        error.code&lt;br /&gt;
                            ? String(&lt;br /&gt;
                                error.code&lt;br /&gt;
                            )&lt;br /&gt;
                            : &#039;UNKNOWN&#039;;&lt;br /&gt;
&lt;br /&gt;
                    status.textContent =&lt;br /&gt;
                        LABELS.uploadFailure +&lt;br /&gt;
                        &#039; [&#039; +&lt;br /&gt;
                        state.uploadErrorCode +&lt;br /&gt;
                        &#039;]&#039;;&lt;br /&gt;
&lt;br /&gt;
                } finally {&lt;br /&gt;
                    uploadButton.disabled =&lt;br /&gt;
                        !state.safeBlob;&lt;br /&gt;
&lt;br /&gt;
                    fileInput.disabled =&lt;br /&gt;
                        false;&lt;br /&gt;
&lt;br /&gt;
                    filenameInput.disabled =&lt;br /&gt;
                        false;&lt;br /&gt;
                }&lt;br /&gt;
                    }())&lt;br /&gt;
                );&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        console.log({&lt;br /&gt;
            R9_3_PRIVACY_UPLOADER_READY:&lt;br /&gt;
                true,&lt;br /&gt;
&lt;br /&gt;
            realUploadEnabled:&lt;br /&gt;
                REAL_UPLOAD_ENABLED,&lt;br /&gt;
&lt;br /&gt;
            standardUploadHidden:&lt;br /&gt;
                uploadLink.hidden,&lt;br /&gt;
&lt;br /&gt;
            mainImagePreserved:&lt;br /&gt;
                mainImage.value ===&lt;br /&gt;
                originalState.mainImage,&lt;br /&gt;
&lt;br /&gt;
            positionStatusPreserved:&lt;br /&gt;
                positionStatus.value ===&lt;br /&gt;
                originalState.positionStatus,&lt;br /&gt;
&lt;br /&gt;
            gpsValuesPrinted:&lt;br /&gt;
                false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.loader&lt;br /&gt;
        .using(&lt;br /&gt;
            &#039;mediawiki.api&#039;&lt;br /&gt;
        )&lt;br /&gt;
        .then(&lt;br /&gt;
            function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    document.readyState ===&lt;br /&gt;
                    &#039;loading&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    document.addEventListener(&lt;br /&gt;
                        &#039;DOMContentLoaded&#039;,&lt;br /&gt;
                        initUploader,&lt;br /&gt;
                        {&lt;br /&gt;
                            once:&lt;br /&gt;
                                true&lt;br /&gt;
                        }&lt;br /&gt;
                    );&lt;br /&gt;
                } else {&lt;br /&gt;
                    initUploader();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    mw.hook&lt;br /&gt;
                ) {&lt;br /&gt;
                    mw.hook(&lt;br /&gt;
                        &#039;wikipage.content&#039;&lt;br /&gt;
                    ).add(&lt;br /&gt;
                        initUploader&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
/* =====================================&lt;br /&gt;
 * R12-01 Stall new-form name autofill&lt;br /&gt;
 * ===================================== */&lt;br /&gt;
( function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initR12StallNameAutofill() {&lt;br /&gt;
        const canonicalSpecial =&lt;br /&gt;
            mw.config.get(&lt;br /&gt;
                &#039;wgCanonicalSpecialPageName&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const targetName =&lt;br /&gt;
            String(&lt;br /&gt;
                mw.config.get(&lt;br /&gt;
                    &#039;wgPageFormsTargetName&#039;&lt;br /&gt;
                ) || &#039;&#039;&lt;br /&gt;
            ).trim();&lt;br /&gt;
&lt;br /&gt;
        const form =&lt;br /&gt;
            document.querySelector(&lt;br /&gt;
                &#039;#pfForm&#039;&lt;br /&gt;
            );&lt;br /&gt;
&lt;br /&gt;
        const nameField =&lt;br /&gt;
            form&lt;br /&gt;
                ? form.querySelector(&lt;br /&gt;
                    &#039;[name=&amp;quot;Stall[name]&amp;quot;]&#039;&lt;br /&gt;
                )&lt;br /&gt;
                : null;&lt;br /&gt;
&lt;br /&gt;
        if (&lt;br /&gt;
            canonicalSpecial !== &#039;FormEdit&#039; ||&lt;br /&gt;
            targetName === &#039;&#039; ||&lt;br /&gt;
            targetName === &#039;Dummy title&#039; ||&lt;br /&gt;
            !nameField ||&lt;br /&gt;
            nameField.value.trim() !== &#039;&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nameField.value =&lt;br /&gt;
            targetName;&lt;br /&gt;
&lt;br /&gt;
        nameField.dispatchEvent(&lt;br /&gt;
            new Event(&lt;br /&gt;
                &#039;input&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    bubbles: true&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        nameField.dispatchEvent(&lt;br /&gt;
            new Event(&lt;br /&gt;
                &#039;change&#039;,&lt;br /&gt;
                {&lt;br /&gt;
                    bubbles: true&lt;br /&gt;
                }&lt;br /&gt;
            )&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        document.readyState === &#039;loading&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        document.addEventListener(&lt;br /&gt;
            &#039;DOMContentLoaded&#039;,&lt;br /&gt;
            initR12StallNameAutofill,&lt;br /&gt;
            {&lt;br /&gt;
                once: true&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    } else {&lt;br /&gt;
        initR12StallNameAutofill();&lt;br /&gt;
    }&lt;br /&gt;
}() );&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * R12-03G2 屋台種類カテゴリ絞り込み&lt;br /&gt;
 *&lt;br /&gt;
 * FestivalStallPlacement の屋台種類 combobox に、&lt;br /&gt;
 * 保存されないカテゴリ絞り込み UI を追加する。&lt;br /&gt;
 *&lt;br /&gt;
 * - カテゴリは Cargo Stalls.category から動的取得&lt;br /&gt;
 * - 「すべて」では通常の Page Forms autocomplete&lt;br /&gt;
 * - カテゴリ選択時は Page Forms 標準 dependent Cargo autocomplete&lt;br /&gt;
 * - helper field は proxy form に所属させ、#pfForm には送信しない&lt;br /&gt;
 */&lt;br /&gt;
( function () {&lt;br /&gt;
	&#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
	const STALL_FIELD = &#039;FestivalStallPlacement[stall_id]&#039;;&lt;br /&gt;
	const TARGET_NAME = &#039;R12StallSearchTarget&#039;;&lt;br /&gt;
	const CATEGORY_NAME = &#039;R12StallCategoryFilter&#039;;&lt;br /&gt;
&lt;br /&gt;
	const PROXY_FORM_ID = &#039;r12-stall-filter-proxy-form&#039;;&lt;br /&gt;
	const FILTER_ID = &#039;r12-stall-category-filter&#039;;&lt;br /&gt;
	const ALL_VALUE = &#039;__all__&#039;;&lt;br /&gt;
&lt;br /&gt;
	const DEPENDENT_PAIR = [&lt;br /&gt;
		CATEGORY_NAME,&lt;br /&gt;
		TARGET_NAME&lt;br /&gt;
	];&lt;br /&gt;
&lt;br /&gt;
	function isOurDependentPair( pair ) {&lt;br /&gt;
		return (&lt;br /&gt;
			Array.isArray( pair ) &amp;amp;&amp;amp;&lt;br /&gt;
			pair.length &amp;gt;= 2 &amp;amp;&amp;amp;&lt;br /&gt;
			pair[ 0 ] === CATEGORY_NAME &amp;amp;&amp;amp;&lt;br /&gt;
			pair[ 1 ] === TARGET_NAME&lt;br /&gt;
		);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function setDependentFilterEnabled( enabled ) {&lt;br /&gt;
		const current =&lt;br /&gt;
			mw.config.get( &#039;wgPageFormsDependentFields&#039; ) || [];&lt;br /&gt;
&lt;br /&gt;
		const next = current.filter( function ( pair ) {&lt;br /&gt;
			return !isOurDependentPair( pair );&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		if ( enabled ) {&lt;br /&gt;
			next.push( [&lt;br /&gt;
				CATEGORY_NAME,&lt;br /&gt;
				TARGET_NAME&lt;br /&gt;
			] );&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		mw.config.set(&lt;br /&gt;
			&#039;wgPageFormsDependentFields&#039;,&lt;br /&gt;
			next&lt;br /&gt;
		);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function extractCategories( response ) {&lt;br /&gt;
		const rows =&lt;br /&gt;
			response &amp;amp;&amp;amp;&lt;br /&gt;
			Array.isArray( response.cargoquery )&lt;br /&gt;
				? response.cargoquery&lt;br /&gt;
				: [];&lt;br /&gt;
&lt;br /&gt;
		const seen = new Set();&lt;br /&gt;
&lt;br /&gt;
		rows.forEach( function ( row ) {&lt;br /&gt;
			const value =&lt;br /&gt;
				row &amp;amp;&amp;amp;&lt;br /&gt;
				row.title&lt;br /&gt;
					? row.title.category&lt;br /&gt;
					: null;&lt;br /&gt;
&lt;br /&gt;
			if (&lt;br /&gt;
				typeof value === &#039;string&#039; &amp;amp;&amp;amp;&lt;br /&gt;
				value.trim() !== &#039;&#039;&lt;br /&gt;
			) {&lt;br /&gt;
				seen.add(&lt;br /&gt;
					value.trim()&lt;br /&gt;
				);&lt;br /&gt;
			}&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return Array.from( seen );&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function createProxyForm() {&lt;br /&gt;
		let proxy =&lt;br /&gt;
			document.getElementById(&lt;br /&gt;
				PROXY_FORM_ID&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if ( proxy ) {&lt;br /&gt;
			return proxy;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		proxy =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;form&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		proxy.id =&lt;br /&gt;
			PROXY_FORM_ID;&lt;br /&gt;
&lt;br /&gt;
		proxy.hidden =&lt;br /&gt;
			true;&lt;br /&gt;
&lt;br /&gt;
		document.body.appendChild(&lt;br /&gt;
			proxy&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		return proxy;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function installFilter(&lt;br /&gt;
		pfForm,&lt;br /&gt;
		hidden,&lt;br /&gt;
		span,&lt;br /&gt;
		visible,&lt;br /&gt;
		categories&lt;br /&gt;
	) {&lt;br /&gt;
		if (&lt;br /&gt;
			document.getElementById(&lt;br /&gt;
				FILTER_ID&lt;br /&gt;
			)&lt;br /&gt;
		) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		const cell =&lt;br /&gt;
			span.closest(&lt;br /&gt;
				&#039;td&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if ( !cell ) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		createProxyForm();&lt;br /&gt;
&lt;br /&gt;
		const wrapper =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;div&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		wrapper.id =&lt;br /&gt;
			FILTER_ID;&lt;br /&gt;
&lt;br /&gt;
		wrapper.style.marginBottom =&lt;br /&gt;
			&#039;10px&#039;;&lt;br /&gt;
&lt;br /&gt;
		const label =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;label&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		label.textContent =&lt;br /&gt;
			&#039;カテゴリで絞り込み（任意）&#039;;&lt;br /&gt;
&lt;br /&gt;
		label.style.display =&lt;br /&gt;
			&#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
		label.style.fontWeight =&lt;br /&gt;
			&#039;600&#039;;&lt;br /&gt;
&lt;br /&gt;
		label.style.marginBottom =&lt;br /&gt;
			&#039;4px&#039;;&lt;br /&gt;
&lt;br /&gt;
		const select =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;select&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		select.name =&lt;br /&gt;
			CATEGORY_NAME;&lt;br /&gt;
&lt;br /&gt;
		select.setAttribute(&lt;br /&gt;
			&#039;form&#039;,&lt;br /&gt;
			PROXY_FORM_ID&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		select.setAttribute(&lt;br /&gt;
			&#039;autocompletesettings&#039;,&lt;br /&gt;
			&#039;Stalls|category&#039;&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		select.setAttribute(&lt;br /&gt;
			&#039;aria-label&#039;,&lt;br /&gt;
			&#039;屋台の種類をカテゴリで絞り込み&#039;&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		select.style.width =&lt;br /&gt;
			&#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
		select.style.maxWidth =&lt;br /&gt;
			&#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
		select.style.boxSizing =&lt;br /&gt;
			&#039;border-box&#039;;&lt;br /&gt;
&lt;br /&gt;
		const allOption =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;option&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		allOption.value =&lt;br /&gt;
			ALL_VALUE;&lt;br /&gt;
&lt;br /&gt;
		allOption.textContent =&lt;br /&gt;
			&#039;すべて&#039;;&lt;br /&gt;
&lt;br /&gt;
		select.appendChild(&lt;br /&gt;
			allOption&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		categories.forEach( function ( category ) {&lt;br /&gt;
			const option =&lt;br /&gt;
				document.createElement(&lt;br /&gt;
					&#039;option&#039;&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
			option.value =&lt;br /&gt;
				category;&lt;br /&gt;
&lt;br /&gt;
			option.textContent =&lt;br /&gt;
				category;&lt;br /&gt;
&lt;br /&gt;
			select.appendChild(&lt;br /&gt;
				option&lt;br /&gt;
			);&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		const help =&lt;br /&gt;
			document.createElement(&lt;br /&gt;
				&#039;div&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		help.className =&lt;br /&gt;
			&#039;stall-form-help&#039;;&lt;br /&gt;
&lt;br /&gt;
		help.textContent =&lt;br /&gt;
			&#039;カテゴリを選ぶと、屋台の種類の検索候補を絞り込めます。&#039;;&lt;br /&gt;
&lt;br /&gt;
		wrapper.appendChild(&lt;br /&gt;
			label&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		wrapper.appendChild(&lt;br /&gt;
			select&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		wrapper.appendChild(&lt;br /&gt;
			help&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * Page Forms dependentOn() が&lt;br /&gt;
		 * visible combobox を識別できるようにする。&lt;br /&gt;
		 *&lt;br /&gt;
		 * form 属性を proxy form に向けるため、&lt;br /&gt;
		 * TARGET_NAME は #pfForm の FormData には入らない。&lt;br /&gt;
		 */&lt;br /&gt;
		visible.setAttribute(&lt;br /&gt;
			&#039;name&#039;,&lt;br /&gt;
			TARGET_NAME&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		visible.setAttribute(&lt;br /&gt;
			&#039;form&#039;,&lt;br /&gt;
			PROXY_FORM_ID&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * R12-03G2-R2&lt;br /&gt;
		 *&lt;br /&gt;
		 * Page Forms の Cargo remote autocomplete は、&lt;br /&gt;
		 * autocompletedatatype=&#039;cargo field&#039; の場合、&lt;br /&gt;
		 * 空文字で dependent autocomplete に到達する前に&lt;br /&gt;
		 * 「1文字以上入力してください」で終了する。&lt;br /&gt;
		 *&lt;br /&gt;
		 * 実カテゴリが選択されているこの屋台種類欄だけ、&lt;br /&gt;
		 * setValues() 実行中に autocompletedatatype を&lt;br /&gt;
		 * 一時的に undefined にし、Page Forms 標準の&lt;br /&gt;
		 * dependent Cargo autocomplete 経路へ通す。&lt;br /&gt;
		 *&lt;br /&gt;
		 * 「すべて」では従来の Cargo autocomplete を維持する。&lt;br /&gt;
		 */&lt;br /&gt;
		const comboPrototype =&lt;br /&gt;
			window.pf &amp;amp;&amp;amp;&lt;br /&gt;
			window.pf.ComboBoxInput &amp;amp;&amp;amp;&lt;br /&gt;
			window.pf.ComboBoxInput.prototype;&lt;br /&gt;
&lt;br /&gt;
		if (&lt;br /&gt;
			comboPrototype &amp;amp;&amp;amp;&lt;br /&gt;
			typeof comboPrototype.setValues === &#039;function&#039; &amp;amp;&amp;amp;&lt;br /&gt;
			comboPrototype.__r12StallDependentCargoBridge !== &#039;1&#039;&lt;br /&gt;
		) {&lt;br /&gt;
			const originalSetValues =&lt;br /&gt;
				comboPrototype.setValues;&lt;br /&gt;
&lt;br /&gt;
			comboPrototype.setValues =&lt;br /&gt;
				function () {&lt;br /&gt;
					const args =&lt;br /&gt;
						arguments;&lt;br /&gt;
&lt;br /&gt;
					const category =&lt;br /&gt;
						document.querySelector(&lt;br /&gt;
							&#039;[name=&amp;quot;&#039; +&lt;br /&gt;
							CATEGORY_NAME +&lt;br /&gt;
							&#039;&amp;quot;]&#039;&lt;br /&gt;
						);&lt;br /&gt;
&lt;br /&gt;
					const isTarget =&lt;br /&gt;
						this.config &amp;amp;&amp;amp;&lt;br /&gt;
						this.config.autocompletesettings ===&lt;br /&gt;
							&#039;Stalls|name&#039; &amp;amp;&amp;amp;&lt;br /&gt;
						typeof this.dependentOn ===&lt;br /&gt;
							&#039;function&#039; &amp;amp;&amp;amp;&lt;br /&gt;
						this.dependentOn() ===&lt;br /&gt;
							CATEGORY_NAME;&lt;br /&gt;
&lt;br /&gt;
					const specificCategory =&lt;br /&gt;
						category &amp;amp;&amp;amp;&lt;br /&gt;
						category.value &amp;amp;&amp;amp;&lt;br /&gt;
						category.value !==&lt;br /&gt;
							ALL_VALUE;&lt;br /&gt;
&lt;br /&gt;
					if (&lt;br /&gt;
						isTarget &amp;amp;&amp;amp;&lt;br /&gt;
						specificCategory &amp;amp;&amp;amp;&lt;br /&gt;
						this.config.autocompletedatatype ===&lt;br /&gt;
							&#039;cargo field&#039;&lt;br /&gt;
					) {&lt;br /&gt;
						const originalDatatype =&lt;br /&gt;
							this.config.autocompletedatatype;&lt;br /&gt;
&lt;br /&gt;
						try {&lt;br /&gt;
							this.config.autocompletedatatype =&lt;br /&gt;
								undefined;&lt;br /&gt;
&lt;br /&gt;
							return originalSetValues.apply(&lt;br /&gt;
								this,&lt;br /&gt;
								args&lt;br /&gt;
							);&lt;br /&gt;
						} finally {&lt;br /&gt;
							this.config.autocompletedatatype =&lt;br /&gt;
								originalDatatype;&lt;br /&gt;
						}&lt;br /&gt;
					}&lt;br /&gt;
&lt;br /&gt;
					return originalSetValues.apply(&lt;br /&gt;
						this,&lt;br /&gt;
						args&lt;br /&gt;
					);&lt;br /&gt;
				};&lt;br /&gt;
&lt;br /&gt;
			comboPrototype.__r12StallDependentCargoBridge =&lt;br /&gt;
				&#039;1&#039;;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		select.addEventListener(&lt;br /&gt;
			&#039;change&#039;,&lt;br /&gt;
			function () {&lt;br /&gt;
				const filterEnabled =&lt;br /&gt;
					select.value !==&lt;br /&gt;
						ALL_VALUE;&lt;br /&gt;
&lt;br /&gt;
				setDependentFilterEnabled(&lt;br /&gt;
					filterEnabled&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
				/*&lt;br /&gt;
				 * 実カテゴリへ切り替えた場合、&lt;br /&gt;
				 * 以前のカテゴリの屋台名を検索文字列として&lt;br /&gt;
				 * dependent autocomplete に渡さない。&lt;br /&gt;
				 *&lt;br /&gt;
				 * 本物の hidden input も空にし、&lt;br /&gt;
				 * 新しいカテゴリから選び直してもらう。&lt;br /&gt;
				 */&lt;br /&gt;
				if ( filterEnabled ) {&lt;br /&gt;
					hidden.value =&lt;br /&gt;
						&#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
					visible.value =&lt;br /&gt;
						&#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
					visible.setAttribute(&lt;br /&gt;
						&#039;data-value&#039;,&lt;br /&gt;
						&#039;&#039;&lt;br /&gt;
					);&lt;br /&gt;
&lt;br /&gt;
					visible.setAttribute(&lt;br /&gt;
						&#039;data-label&#039;,&lt;br /&gt;
						&#039;&#039;&lt;br /&gt;
					);&lt;br /&gt;
&lt;br /&gt;
					visible.setAttribute(&lt;br /&gt;
						&#039;title&#039;,&lt;br /&gt;
						&#039;&#039;&lt;br /&gt;
					);&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * 初期状態は「すべて」。&lt;br /&gt;
		 */&lt;br /&gt;
		setDependentFilterEnabled(&lt;br /&gt;
			false&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		cell.insertBefore(&lt;br /&gt;
			wrapper,&lt;br /&gt;
			span&lt;br /&gt;
		);&lt;br /&gt;
&lt;br /&gt;
		span.dataset.r12StallCategoryFilter =&lt;br /&gt;
			&#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
		/*&lt;br /&gt;
		 * Safety invariant:&lt;br /&gt;
		 * 本物の stall_id hidden input は&lt;br /&gt;
		 * #pfForm に所属したまま。&lt;br /&gt;
		 */&lt;br /&gt;
		if (&lt;br /&gt;
			hidden.form !== pfForm&lt;br /&gt;
		) {&lt;br /&gt;
			mw.log.warn(&lt;br /&gt;
				&#039;R12-03G2: stall_id form ownership changed unexpectedly.&#039;&lt;br /&gt;
			);&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function initialize() {&lt;br /&gt;
		const pfForm =&lt;br /&gt;
			document.querySelector(&lt;br /&gt;
				&#039;#pfForm&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if ( !pfForm ) {&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		const hidden =&lt;br /&gt;
			pfForm.querySelector(&lt;br /&gt;
				&#039;input[type=&amp;quot;hidden&amp;quot;][name=&amp;quot;&#039; +&lt;br /&gt;
				STALL_FIELD +&lt;br /&gt;
				&#039;&amp;quot;]&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if ( !hidden ) {&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		const span =&lt;br /&gt;
			hidden.closest(&lt;br /&gt;
				&#039;.comboboxSpan&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if (&lt;br /&gt;
			!span ||&lt;br /&gt;
			span.dataset.r12StallCategoryFilter ===&lt;br /&gt;
				&#039;1&#039; ||&lt;br /&gt;
			span.dataset.r12StallCategoryFilterLoading ===&lt;br /&gt;
				&#039;1&#039;&lt;br /&gt;
		) {&lt;br /&gt;
			return !!span;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		const visible =&lt;br /&gt;
			span.querySelector(&lt;br /&gt;
				&#039;input[role=&amp;quot;combobox&amp;quot;]&#039;&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		if (&lt;br /&gt;
			!visible ||&lt;br /&gt;
			visible.getAttribute(&lt;br /&gt;
				&#039;autocompletesettings&#039;&lt;br /&gt;
			) !== &#039;Stalls|name&#039;&lt;br /&gt;
		) {&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		span.dataset.r12StallCategoryFilterLoading =&lt;br /&gt;
			&#039;1&#039;;&lt;br /&gt;
&lt;br /&gt;
		mw.loader.using(&lt;br /&gt;
			&#039;mediawiki.api&#039;&lt;br /&gt;
		).then( function () {&lt;br /&gt;
			const api =&lt;br /&gt;
				new mw.Api();&lt;br /&gt;
&lt;br /&gt;
			return api.get( {&lt;br /&gt;
				action:&lt;br /&gt;
					&#039;cargoquery&#039;,&lt;br /&gt;
&lt;br /&gt;
				tables:&lt;br /&gt;
					&#039;Stalls&#039;,&lt;br /&gt;
&lt;br /&gt;
				fields:&lt;br /&gt;
					&#039;category&#039;,&lt;br /&gt;
&lt;br /&gt;
				where:&lt;br /&gt;
					&amp;quot;category IS NOT NULL AND category != &#039;&#039;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
				group_by:&lt;br /&gt;
					&#039;category&#039;,&lt;br /&gt;
&lt;br /&gt;
				order_by:&lt;br /&gt;
					&#039;category&#039;,&lt;br /&gt;
&lt;br /&gt;
				limit:&lt;br /&gt;
					500,&lt;br /&gt;
&lt;br /&gt;
				format:&lt;br /&gt;
					&#039;json&#039;&lt;br /&gt;
			} );&lt;br /&gt;
&lt;br /&gt;
		} ).then( function ( response ) {&lt;br /&gt;
			const categories =&lt;br /&gt;
				extractCategories(&lt;br /&gt;
					response&lt;br /&gt;
				);&lt;br /&gt;
&lt;br /&gt;
			if (&lt;br /&gt;
				categories.length === 0&lt;br /&gt;
			) {&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			installFilter(&lt;br /&gt;
				pfForm,&lt;br /&gt;
				hidden,&lt;br /&gt;
				span,&lt;br /&gt;
				visible,&lt;br /&gt;
				categories&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		} ).catch( function ( error ) {&lt;br /&gt;
			mw.log.warn(&lt;br /&gt;
				&#039;R12-03G2: category filter initialization failed.&#039;,&lt;br /&gt;
				error&lt;br /&gt;
			);&lt;br /&gt;
&lt;br /&gt;
		} ).always( function () {&lt;br /&gt;
			delete span.dataset&lt;br /&gt;
				.r12StallCategoryFilterLoading;&lt;br /&gt;
		} );&lt;br /&gt;
&lt;br /&gt;
		return true;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function start() {&lt;br /&gt;
		let attempts =&lt;br /&gt;
			0;&lt;br /&gt;
&lt;br /&gt;
		const maxAttempts =&lt;br /&gt;
			50;&lt;br /&gt;
&lt;br /&gt;
		function tryInitialize() {&lt;br /&gt;
			attempts +=&lt;br /&gt;
				1;&lt;br /&gt;
&lt;br /&gt;
			if (&lt;br /&gt;
				initialize() ||&lt;br /&gt;
				attempts &amp;gt;=&lt;br /&gt;
					maxAttempts&lt;br /&gt;
			) {&lt;br /&gt;
				return;&lt;br /&gt;
			}&lt;br /&gt;
&lt;br /&gt;
			window.setTimeout(&lt;br /&gt;
				tryInitialize,&lt;br /&gt;
				100&lt;br /&gt;
			);&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		tryInitialize();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	if (&lt;br /&gt;
		document.readyState ===&lt;br /&gt;
			&#039;loading&#039;&lt;br /&gt;
	) {&lt;br /&gt;
		document.addEventListener(&lt;br /&gt;
			&#039;DOMContentLoaded&#039;,&lt;br /&gt;
			start,&lt;br /&gt;
			{&lt;br /&gt;
				once:&lt;br /&gt;
					true&lt;br /&gt;
			}&lt;br /&gt;
		);&lt;br /&gt;
	} else {&lt;br /&gt;
		start();&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
}() );&lt;/div&gt;</summary>
		<author><name>R12 Common.js update</name></author>
	</entry>
</feed>