編集の要約なし |
編集の要約なし |
||
| 1行目: | 1行目: | ||
/* ======================================== | /* ======================================== | ||
* 屋台比較 | * 屋台比較 | ||
* | * | ||
* | * ・最大4店舗 | ||
* | * ・localStorageにはstall_idだけ保存 | ||
* ・屋台名はCargo APIから取得 | |||
* ・画面下部に固定比較トレイを表示 | |||
* ======================================== */ | * ======================================== */ | ||
mw.loader.using( 'mediawiki.storage' ).then( function () { | mw.loader.using( [ | ||
'mediawiki.storage', | |||
'mediawiki.api', | |||
'mediawiki.util' | |||
] ).then( function () { | |||
'use strict'; | 'use strict'; | ||
| 13行目: | 19行目: | ||
const MAX_COMPARE = 4; | const MAX_COMPARE = 4; | ||
/** | const api = new mw.Api(); | ||
* | |||
/* | |||
* このページを表示している間だけ使う | |||
* 屋台名キャッシュ | |||
*/ | */ | ||
const stallNameCache = {}; | |||
/* ===================================== | |||
* localStorage | |||
* ===================================== */ | |||
function getCompareStalls() { | function getCompareStalls() { | ||
| 32行目: | 48行目: | ||
} | } | ||
/* | |||
* 数字だけ許可 | |||
* localStorage改変対策 | |||
*/ | |||
return [ ...new Set( | return [ ...new Set( | ||
ids | ids | ||
.map( String ) | .map( String ) | ||
.filter( function ( id ) { | .filter( function ( id ) { | ||
return id | return /^\d+$/.test( id ); | ||
} ) | } ) | ||
) ].slice( 0, MAX_COMPARE ); | ) ].slice( 0, MAX_COMPARE ); | ||
| 45行目: | 65行目: | ||
} | } | ||
} | } | ||
function saveCompareStalls( ids ) { | function saveCompareStalls( ids ) { | ||
| 61行目: | 79行目: | ||
/** | /* ===================================== | ||
* | * Cargoから屋台名取得 | ||
*/ | * ===================================== */ | ||
function showMessage( control, message, isError ) { | |||
function fetchStallNames( ids ) { | |||
if ( ids.length === 0 ) { | |||
return Promise.resolve( {} ); | |||
} | |||
/* | |||
* すでに取得済みのID | |||
*/ | |||
const result = {}; | |||
const missingIds = []; | |||
ids.forEach( function ( id ) { | |||
if ( stallNameCache[ id ] ) { | |||
result[ id ] = | |||
stallNameCache[ id ]; | |||
} else { | |||
missingIds.push( id ); | |||
} | |||
} ); | |||
/* | |||
* 全部キャッシュ済み | |||
*/ | |||
if ( missingIds.length === 0 ) { | |||
return Promise.resolve( result ); | |||
} | |||
/* | |||
* 数字だけにしてあるため | |||
* IN (1,2,3) として安全にCargoへ渡す | |||
*/ | |||
const where = | |||
'stall_id IN (' + | |||
missingIds.join( ',' ) + | |||
')'; | |||
return api.get( { | |||
action: 'cargoquery', | |||
tables: 'Stalls', | |||
fields: | |||
'stall_id=stall_id,' + | |||
'name=stall_name,' + | |||
'_pageName=page_name', | |||
where: where, | |||
limit: MAX_COMPARE, | |||
format: 'json' | |||
} ).then( function ( data ) { | |||
if ( | |||
!data || | |||
!Array.isArray( data.cargoquery ) | |||
) { | |||
return result; | |||
} | |||
data.cargoquery.forEach( function ( item ) { | |||
/* | |||
* Cargo APIは通常 | |||
* | |||
* { | |||
* title: { | |||
* stall_id: "...", | |||
* stall_name: "..." | |||
* } | |||
* } | |||
* | |||
* の形式 | |||
*/ | |||
const row = | |||
item.title || item; | |||
if ( | |||
!row || | |||
row.stall_id === undefined | |||
) { | |||
return; | |||
} | |||
const id = | |||
String( row.stall_id ); | |||
const info = { | |||
id: id, | |||
name: | |||
row.stall_name || | |||
'屋台ID ' + id, | |||
page: | |||
row.page_name || '' | |||
}; | |||
stallNameCache[ id ] = info; | |||
result[ id ] = info; | |||
} ); | |||
return result; | |||
} ).catch( function () { | |||
/* | |||
* APIエラーでも | |||
* 比較機能そのものは壊さない | |||
*/ | |||
missingIds.forEach( function ( id ) { | |||
result[ id ] = { | |||
id: id, | |||
name: '屋台ID ' + id, | |||
page: '' | |||
}; | |||
} ); | |||
return result; | |||
} ); | |||
} | |||
/* ===================================== | |||
* 詳細ページ側メッセージ | |||
* ===================================== */ | |||
function showMessage( | |||
control, | |||
message, | |||
isError | |||
) { | |||
const | const element = | ||
control.querySelector( | control.querySelector( | ||
'.stall-compare-message' | '.stall-compare-message' | ||
); | ); | ||
if ( ! | if ( !element ) { | ||
return; | return; | ||
} | } | ||
element.textContent = | |||
message; | |||
element.classList.toggle( | |||
'stall-compare-message-error', | 'stall-compare-message-error', | ||
Boolean( isError ) | Boolean( isError ) | ||
| 85行目: | 258行目: | ||
/* | /* ===================================== | ||
* | * 詳細ページ側button生成 | ||
*/ | * ===================================== */ | ||
function createCompareButtons() { | function createCompareButtons() { | ||
| 96行目: | 270行目: | ||
.forEach( function ( placeholder ) { | .forEach( function ( placeholder ) { | ||
if ( | if ( | ||
placeholder.dataset.initialized === '1' | placeholder.dataset.initialized === | ||
'1' | |||
) { | ) { | ||
return; | return; | ||
} | } | ||
const prefix = | const prefix = | ||
'stall-compare-placeholder-'; | 'stall-compare-placeholder-'; | ||
if ( | if ( | ||
!placeholder.id.startsWith( prefix ) | !placeholder.id.startsWith( | ||
prefix | |||
) | |||
) { | ) { | ||
return; | return; | ||
| 125行目: | 293行目: | ||
); | ); | ||
if ( !stallId ) { | if ( | ||
!/^\d+$/.test( stallId ) | |||
) { | |||
return; | return; | ||
} | } | ||
const button = | const button = | ||
document.createElement( 'button' ); | document.createElement( | ||
'button' | |||
); | |||
button.type = 'button'; | button.type = | ||
'button'; | |||
button.className = | button.className = | ||
| 151行目: | 321行目: | ||
'比較に追加'; | '比較に追加'; | ||
placeholder.appendChild( button ); | placeholder.appendChild( | ||
button | |||
); | |||
placeholder.dataset.initialized = | placeholder.dataset.initialized = | ||
| 161行目: | 333行目: | ||
/* | /* ===================================== | ||
* | * 詳細ページ側button状態 | ||
*/ | * ===================================== */ | ||
function updateCompareButtons() { | function updateCompareButtons() { | ||
| 218行目: | 391行目: | ||
/** | /* ===================================== | ||
* | * 固定比較トレイを生成 | ||
*/ | * ===================================== */ | ||
function createCompareTray() { | |||
if ( | |||
document.getElementById( | |||
'stall-compare-tray' | |||
) | |||
) { | |||
return; | |||
} | |||
const tray = | |||
document.createElement( 'div' ); | |||
tray.id = | |||
'stall-compare-tray'; | |||
tray.className = | |||
'stall-compare-tray'; | |||
tray.setAttribute( | |||
'aria-live', | |||
'polite' | |||
); | |||
/* | |||
* ヘッダー | |||
*/ | |||
const header = | |||
document.createElement( 'div' ); | |||
header.className = | |||
'stall-compare-tray-header'; | |||
const title = | |||
document.createElement( 'strong' ); | |||
title.className = | |||
'stall-compare-tray-title'; | |||
title.textContent = | |||
'比較候補'; | |||
const count = | |||
document.createElement( 'span' ); | |||
count.className = | |||
'stall-compare-tray-count'; | |||
header.appendChild( title ); | |||
header.appendChild( count ); | |||
/* | |||
* 屋台一覧 | |||
*/ | |||
const items = | |||
document.createElement( 'div' ); | |||
items.className = | |||
'stall-compare-tray-items'; | |||
/* | |||
* アクション部分 | |||
*/ | |||
const actions = | |||
document.createElement( 'div' ); | |||
actions.className = | |||
'stall-compare-tray-actions'; | |||
const clearButton = | |||
document.createElement( 'button' ); | |||
clearButton.type = | |||
'button'; | |||
clearButton.className = | |||
'stall-compare-clear'; | |||
clearButton.textContent = | |||
'すべて外す'; | |||
const compareLink = | |||
document.createElement( 'a' ); | |||
compareLink.className = | |||
'stall-compare-open'; | |||
compareLink.href = | |||
mw.util.getUrl( '屋台比較' ); | |||
compareLink.textContent = | |||
'比較する'; | |||
actions.appendChild( | |||
clearButton | |||
); | |||
actions.appendChild( | |||
compareLink | |||
); | |||
tray.appendChild( | |||
header | |||
); | |||
tray.appendChild( | |||
items | |||
); | |||
tray.appendChild( | |||
actions | |||
); | |||
document.body.appendChild( | |||
tray | |||
); | |||
} | |||
/* ===================================== | |||
* 固定比較トレイ更新 | |||
* ===================================== */ | |||
function updateCompareTray() { | |||
createCompareTray(); | |||
const tray = | |||
document.getElementById( | |||
'stall-compare-tray' | |||
); | |||
if ( !tray ) { | |||
return; | |||
} | |||
const ids = | |||
getCompareStalls(); | |||
const count = | |||
tray.querySelector( | |||
'.stall-compare-tray-count' | |||
); | |||
const items = | |||
tray.querySelector( | |||
'.stall-compare-tray-items' | |||
); | |||
const compareLink = | |||
tray.querySelector( | |||
'.stall-compare-open' | |||
); | |||
if ( count ) { | |||
count.textContent = | |||
ids.length + | |||
' / ' + | |||
MAX_COMPARE; | |||
} | |||
/* | |||
* 0件ならトレイ非表示 | |||
*/ | |||
if ( ids.length === 0 ) { | |||
tray.classList.remove( | |||
'stall-compare-tray-visible' | |||
); | |||
if ( items ) { | |||
items.innerHTML = ''; | |||
} | |||
return; | |||
} | |||
tray.classList.add( | |||
'stall-compare-tray-visible' | |||
); | |||
/* | |||
* 2件未満では比較ページを無効化 | |||
*/ | |||
if ( compareLink ) { | |||
if ( ids.length >= 2 ) { | |||
compareLink.classList.remove( | |||
'stall-compare-open-disabled' | |||
); | |||
compareLink.setAttribute( | |||
'aria-disabled', | |||
'false' | |||
); | |||
} else { | |||
compareLink.classList.add( | |||
'stall-compare-open-disabled' | |||
); | |||
compareLink.setAttribute( | |||
'aria-disabled', | |||
'true' | |||
); | |||
} | |||
} | |||
if ( !items ) { | |||
return; | |||
} | |||
/* | |||
* 読み込み中 | |||
*/ | |||
items.textContent = | |||
'屋台情報を読み込み中…'; | |||
fetchStallNames( | |||
ids | |||
).then( function ( stallInfo ) { | |||
/* | |||
* 更新待ち中に選択内容が | |||
* 変わった場合も現在値を優先 | |||
*/ | |||
const currentIds = | |||
getCompareStalls(); | |||
items.innerHTML = | |||
''; | |||
currentIds.forEach( | |||
function ( id ) { | |||
const info = | |||
stallInfo[ id ] || { | |||
id: id, | |||
name: | |||
'屋台ID ' + id, | |||
page: '' | |||
}; | |||
const item = | |||
document.createElement( | |||
'div' | |||
); | |||
item.className = | |||
'stall-compare-tray-item'; | |||
/* | |||
* 屋台名 | |||
*/ | |||
let nameElement; | |||
if ( info.page ) { | |||
nameElement = | |||
document.createElement( | |||
'a' | |||
); | |||
nameElement.href = | |||
mw.util.getUrl( | |||
info.page | |||
); | |||
} else { | |||
nameElement = | |||
document.createElement( | |||
'span' | |||
); | |||
} | |||
nameElement.className = | |||
'stall-compare-tray-item-name'; | |||
nameElement.textContent = | |||
info.name; | |||
/* | |||
* × ボタン | |||
*/ | |||
const removeButton = | |||
document.createElement( | |||
'button' | |||
); | |||
removeButton.type = | |||
'button'; | |||
removeButton.className = | |||
'stall-compare-tray-remove'; | |||
removeButton.dataset.stallId = | |||
id; | |||
removeButton.setAttribute( | |||
'aria-label', | |||
info.name + | |||
'を比較候補から外す' | |||
); | |||
removeButton.textContent = | |||
'×'; | |||
item.appendChild( | |||
nameElement | |||
); | |||
item.appendChild( | |||
removeButton | |||
); | |||
items.appendChild( | |||
item | |||
); | |||
} | |||
); | |||
} ); | |||
} | |||
/* ===================================== | |||
* 詳細ページ比較button | |||
* ===================================== */ | |||
document.addEventListener( | document.addEventListener( | ||
'click', | 'click', | ||
| 240行目: | 779行目: | ||
button.dataset.stallId || '' | button.dataset.stallId || '' | ||
); | ); | ||
if ( | |||
!/^\d+$/.test( stallId ) | |||
) { | |||
return; | |||
} | |||
const control = | const control = | ||
| 246行目: | 792行目: | ||
); | ); | ||
let ids = | let ids = | ||
getCompareStalls(); | getCompareStalls(); | ||
const index = | const index = | ||
ids.indexOf( stallId ); | ids.indexOf( | ||
stallId | |||
); | |||
/* | /* | ||
* | * すでに選択済み | ||
* → | * → 外す | ||
*/ | */ | ||
if ( index !== -1 ) { | if ( index !== -1 ) { | ||
| 284行目: | 819行目: | ||
updateCompareButtons(); | updateCompareButtons(); | ||
updateCompareTray(); | |||
if ( control ) { | if ( control ) { | ||
| 296行目: | 832行目: | ||
return; | return; | ||
} | } | ||
/* | /* | ||
* | * 4件上限 | ||
*/ | */ | ||
if ( | if ( | ||
ids.length >= MAX_COMPARE | ids.length >= | ||
MAX_COMPARE | |||
) { | ) { | ||
| 317行目: | 855行目: | ||
return; | return; | ||
} | } | ||
ids.push( | ids.push( | ||
stallId | stallId | ||
| 332行目: | 868行目: | ||
updateCompareButtons(); | updateCompareButtons(); | ||
updateCompareTray(); | |||
if ( control ) { | if ( control ) { | ||
| 349行目: | 887行目: | ||
/** | /* ===================================== | ||
* トレイ × button | |||
* ===================================== */ | |||
document.addEventListener( | |||
'click', | |||
function ( event ) { | |||
const button = | |||
event.target.closest( | |||
'.stall-compare-tray-remove' | |||
); | |||
if ( !button ) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
const stallId = | |||
String( | |||
button.dataset.stallId || '' | |||
); | |||
let ids = | |||
getCompareStalls(); | |||
ids = | |||
ids.filter( | |||
function ( id ) { | |||
return id !== stallId; | |||
} | |||
); | |||
saveCompareStalls( | |||
ids | |||
); | |||
updateCompareButtons(); | |||
updateCompareTray(); | |||
} | |||
); | |||
/* ===================================== | |||
* 「すべて外す」 | |||
* ===================================== */ | |||
document.addEventListener( | |||
'click', | |||
function ( event ) { | |||
const button = | |||
event.target.closest( | |||
'.stall-compare-clear' | |||
); | |||
if ( !button ) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
saveCompareStalls( | |||
[] | |||
); | |||
updateCompareButtons(); | |||
updateCompareTray(); | |||
} | |||
); | |||
/* ===================================== | |||
* 1件時の「比較する」クリック防止 | |||
* ===================================== */ | |||
document.addEventListener( | |||
'click', | |||
function ( event ) { | |||
const link = | |||
event.target.closest( | |||
'.stall-compare-open-disabled' | |||
); | |||
if ( !link ) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
} | |||
); | |||
/* ===================================== | |||
* 初期化 | * 初期化 | ||
*/ | * ===================================== */ | ||
function initStallCompare() { | function initStallCompare() { | ||
createCompareButtons(); | createCompareButtons(); | ||
createCompareTray(); | |||
updateCompareButtons(); | updateCompareButtons(); | ||
updateCompareTray(); | |||
} | } | ||
initStallCompare(); | initStallCompare(); | ||
mw.hook( | mw.hook( | ||
'wikipage.content' | 'wikipage.content' | ||
2026年8月11日 (火) 20:50時点における版
/* ========================================
* 屋台比較
*
* ・最大4店舗
* ・localStorageにはstall_idだけ保存
* ・屋台名はCargo APIから取得
* ・画面下部に固定比較トレイを表示
* ======================================== */
mw.loader.using( [
'mediawiki.storage',
'mediawiki.api',
'mediawiki.util'
] ).then( function () {
'use strict';
const STORAGE_KEY = 'matsuriWikiCompareStalls';
const MAX_COMPARE = 4;
const api = new mw.Api();
/*
* このページを表示している間だけ使う
* 屋台名キャッシュ
*/
const stallNameCache = {};
/* =====================================
* localStorage
* ===================================== */
function getCompareStalls() {
const raw = mw.storage.get( STORAGE_KEY );
if ( !raw ) {
return [];
}
try {
const ids = JSON.parse( raw );
if ( !Array.isArray( ids ) ) {
return [];
}
/*
* 数字だけ許可
* localStorage改変対策
*/
return [ ...new Set(
ids
.map( String )
.filter( function ( id ) {
return /^\d+$/.test( id );
} )
) ].slice( 0, MAX_COMPARE );
} catch ( e ) {
return [];
}
}
function saveCompareStalls( ids ) {
mw.storage.set(
STORAGE_KEY,
JSON.stringify( ids )
);
}
/* =====================================
* Cargoから屋台名取得
* ===================================== */
function fetchStallNames( ids ) {
if ( ids.length === 0 ) {
return Promise.resolve( {} );
}
/*
* すでに取得済みのID
*/
const result = {};
const missingIds = [];
ids.forEach( function ( id ) {
if ( stallNameCache[ id ] ) {
result[ id ] =
stallNameCache[ id ];
} else {
missingIds.push( id );
}
} );
/*
* 全部キャッシュ済み
*/
if ( missingIds.length === 0 ) {
return Promise.resolve( result );
}
/*
* 数字だけにしてあるため
* IN (1,2,3) として安全にCargoへ渡す
*/
const where =
'stall_id IN (' +
missingIds.join( ',' ) +
')';
return api.get( {
action: 'cargoquery',
tables: 'Stalls',
fields:
'stall_id=stall_id,' +
'name=stall_name,' +
'_pageName=page_name',
where: where,
limit: MAX_COMPARE,
format: 'json'
} ).then( function ( data ) {
if (
!data ||
!Array.isArray( data.cargoquery )
) {
return result;
}
data.cargoquery.forEach( function ( item ) {
/*
* Cargo APIは通常
*
* {
* title: {
* stall_id: "...",
* stall_name: "..."
* }
* }
*
* の形式
*/
const row =
item.title || item;
if (
!row ||
row.stall_id === undefined
) {
return;
}
const id =
String( row.stall_id );
const info = {
id: id,
name:
row.stall_name ||
'屋台ID ' + id,
page:
row.page_name || ''
};
stallNameCache[ id ] = info;
result[ id ] = info;
} );
return result;
} ).catch( function () {
/*
* APIエラーでも
* 比較機能そのものは壊さない
*/
missingIds.forEach( function ( id ) {
result[ id ] = {
id: id,
name: '屋台ID ' + id,
page: ''
};
} );
return result;
} );
}
/* =====================================
* 詳細ページ側メッセージ
* ===================================== */
function showMessage(
control,
message,
isError
) {
const element =
control.querySelector(
'.stall-compare-message'
);
if ( !element ) {
return;
}
element.textContent =
message;
element.classList.toggle(
'stall-compare-message-error',
Boolean( isError )
);
}
/* =====================================
* 詳細ページ側button生成
* ===================================== */
function createCompareButtons() {
document
.querySelectorAll(
'.stall-compare-placeholder'
)
.forEach( function ( placeholder ) {
if (
placeholder.dataset.initialized ===
'1'
) {
return;
}
const prefix =
'stall-compare-placeholder-';
if (
!placeholder.id.startsWith(
prefix
)
) {
return;
}
const stallId =
placeholder.id.substring(
prefix.length
);
if (
!/^\d+$/.test( stallId )
) {
return;
}
const button =
document.createElement(
'button'
);
button.type =
'button';
button.className =
'stall-compare-button';
button.dataset.stallId =
stallId;
button.setAttribute(
'aria-pressed',
'false'
);
button.textContent =
'比較に追加';
placeholder.appendChild(
button
);
placeholder.dataset.initialized =
'1';
} );
}
/* =====================================
* 詳細ページ側button状態
* ===================================== */
function updateCompareButtons() {
const ids =
getCompareStalls();
document
.querySelectorAll(
'.stall-compare-button'
)
.forEach( function ( button ) {
const stallId =
String(
button.dataset.stallId || ''
);
const selected =
ids.includes( stallId );
if ( selected ) {
button.textContent =
'比較から外す';
button.classList.add(
'stall-compare-button-selected'
);
button.setAttribute(
'aria-pressed',
'true'
);
} else {
button.textContent =
'比較に追加';
button.classList.remove(
'stall-compare-button-selected'
);
button.setAttribute(
'aria-pressed',
'false'
);
}
} );
}
/* =====================================
* 固定比較トレイを生成
* ===================================== */
function createCompareTray() {
if (
document.getElementById(
'stall-compare-tray'
)
) {
return;
}
const tray =
document.createElement( 'div' );
tray.id =
'stall-compare-tray';
tray.className =
'stall-compare-tray';
tray.setAttribute(
'aria-live',
'polite'
);
/*
* ヘッダー
*/
const header =
document.createElement( 'div' );
header.className =
'stall-compare-tray-header';
const title =
document.createElement( 'strong' );
title.className =
'stall-compare-tray-title';
title.textContent =
'比較候補';
const count =
document.createElement( 'span' );
count.className =
'stall-compare-tray-count';
header.appendChild( title );
header.appendChild( count );
/*
* 屋台一覧
*/
const items =
document.createElement( 'div' );
items.className =
'stall-compare-tray-items';
/*
* アクション部分
*/
const actions =
document.createElement( 'div' );
actions.className =
'stall-compare-tray-actions';
const clearButton =
document.createElement( 'button' );
clearButton.type =
'button';
clearButton.className =
'stall-compare-clear';
clearButton.textContent =
'すべて外す';
const compareLink =
document.createElement( 'a' );
compareLink.className =
'stall-compare-open';
compareLink.href =
mw.util.getUrl( '屋台比較' );
compareLink.textContent =
'比較する';
actions.appendChild(
clearButton
);
actions.appendChild(
compareLink
);
tray.appendChild(
header
);
tray.appendChild(
items
);
tray.appendChild(
actions
);
document.body.appendChild(
tray
);
}
/* =====================================
* 固定比較トレイ更新
* ===================================== */
function updateCompareTray() {
createCompareTray();
const tray =
document.getElementById(
'stall-compare-tray'
);
if ( !tray ) {
return;
}
const ids =
getCompareStalls();
const count =
tray.querySelector(
'.stall-compare-tray-count'
);
const items =
tray.querySelector(
'.stall-compare-tray-items'
);
const compareLink =
tray.querySelector(
'.stall-compare-open'
);
if ( count ) {
count.textContent =
ids.length +
' / ' +
MAX_COMPARE;
}
/*
* 0件ならトレイ非表示
*/
if ( ids.length === 0 ) {
tray.classList.remove(
'stall-compare-tray-visible'
);
if ( items ) {
items.innerHTML = '';
}
return;
}
tray.classList.add(
'stall-compare-tray-visible'
);
/*
* 2件未満では比較ページを無効化
*/
if ( compareLink ) {
if ( ids.length >= 2 ) {
compareLink.classList.remove(
'stall-compare-open-disabled'
);
compareLink.setAttribute(
'aria-disabled',
'false'
);
} else {
compareLink.classList.add(
'stall-compare-open-disabled'
);
compareLink.setAttribute(
'aria-disabled',
'true'
);
}
}
if ( !items ) {
return;
}
/*
* 読み込み中
*/
items.textContent =
'屋台情報を読み込み中…';
fetchStallNames(
ids
).then( function ( stallInfo ) {
/*
* 更新待ち中に選択内容が
* 変わった場合も現在値を優先
*/
const currentIds =
getCompareStalls();
items.innerHTML =
'';
currentIds.forEach(
function ( id ) {
const info =
stallInfo[ id ] || {
id: id,
name:
'屋台ID ' + id,
page: ''
};
const item =
document.createElement(
'div'
);
item.className =
'stall-compare-tray-item';
/*
* 屋台名
*/
let nameElement;
if ( info.page ) {
nameElement =
document.createElement(
'a'
);
nameElement.href =
mw.util.getUrl(
info.page
);
} else {
nameElement =
document.createElement(
'span'
);
}
nameElement.className =
'stall-compare-tray-item-name';
nameElement.textContent =
info.name;
/*
* × ボタン
*/
const removeButton =
document.createElement(
'button'
);
removeButton.type =
'button';
removeButton.className =
'stall-compare-tray-remove';
removeButton.dataset.stallId =
id;
removeButton.setAttribute(
'aria-label',
info.name +
'を比較候補から外す'
);
removeButton.textContent =
'×';
item.appendChild(
nameElement
);
item.appendChild(
removeButton
);
items.appendChild(
item
);
}
);
} );
}
/* =====================================
* 詳細ページ比較button
* ===================================== */
document.addEventListener(
'click',
function ( event ) {
const button =
event.target.closest(
'.stall-compare-button'
);
if ( !button ) {
return;
}
event.preventDefault();
const stallId =
String(
button.dataset.stallId || ''
);
if (
!/^\d+$/.test( stallId )
) {
return;
}
const control =
button.closest(
'.stall-compare-control'
);
let ids =
getCompareStalls();
const index =
ids.indexOf(
stallId
);
/*
* すでに選択済み
* → 外す
*/
if ( index !== -1 ) {
ids.splice(
index,
1
);
saveCompareStalls(
ids
);
updateCompareButtons();
updateCompareTray();
if ( control ) {
showMessage(
control,
'比較候補から外しました。',
false
);
}
return;
}
/*
* 4件上限
*/
if (
ids.length >=
MAX_COMPARE
) {
if ( control ) {
showMessage(
control,
'比較できる屋台は最大4店舗です。',
true
);
}
return;
}
ids.push(
stallId
);
saveCompareStalls(
ids
);
updateCompareButtons();
updateCompareTray();
if ( control ) {
showMessage(
control,
'比較候補に追加しました(' +
ids.length +
'/4)。',
false
);
}
}
);
/* =====================================
* トレイ × button
* ===================================== */
document.addEventListener(
'click',
function ( event ) {
const button =
event.target.closest(
'.stall-compare-tray-remove'
);
if ( !button ) {
return;
}
event.preventDefault();
const stallId =
String(
button.dataset.stallId || ''
);
let ids =
getCompareStalls();
ids =
ids.filter(
function ( id ) {
return id !== stallId;
}
);
saveCompareStalls(
ids
);
updateCompareButtons();
updateCompareTray();
}
);
/* =====================================
* 「すべて外す」
* ===================================== */
document.addEventListener(
'click',
function ( event ) {
const button =
event.target.closest(
'.stall-compare-clear'
);
if ( !button ) {
return;
}
event.preventDefault();
saveCompareStalls(
[]
);
updateCompareButtons();
updateCompareTray();
}
);
/* =====================================
* 1件時の「比較する」クリック防止
* ===================================== */
document.addEventListener(
'click',
function ( event ) {
const link =
event.target.closest(
'.stall-compare-open-disabled'
);
if ( !link ) {
return;
}
event.preventDefault();
}
);
/* =====================================
* 初期化
* ===================================== */
function initStallCompare() {
createCompareButtons();
createCompareTray();
updateCompareButtons();
updateCompareTray();
}
initStallCompare();
mw.hook(
'wikipage.content'
).add(
function () {
initStallCompare();
}
);
} );