ページの作成:「→ここにあるすべてのJavaScriptは、すべてのページ読み込みですべての利用者に対して読み込まれます: →======================================== * 屋台比較 * 最大4店舗を localStorage に保存 * ========================================: mw.loader.using( 'mediawiki.storage' ).then( function () { 'use strict'; const STORAGE_KEY = 'matsuriWikiCompareStalls'; const MAX_COMPARE = 4; /** * 比…」 |
編集の要約なし |
||
| 1行目: | 1行目: | ||
/* ======================================== | /* ======================================== | ||
* 屋台比較 | * 屋台比較 | ||
* 最大4店舗を localStorage に保存 | * 最大4店舗を localStorage に保存 | ||
* Template側にはbuttonを書かず、 | |||
* JavaScriptでbuttonを生成する | |||
* ======================================== */ | * ======================================== */ | ||
| 16行目: | 17行目: | ||
*/ | */ | ||
function getCompareStalls() { | function getCompareStalls() { | ||
const raw = mw.storage.get( STORAGE_KEY ); | const raw = mw.storage.get( STORAGE_KEY ); | ||
| 23行目: | 25行目: | ||
try { | try { | ||
const ids = JSON.parse( raw ); | const ids = JSON.parse( raw ); | ||
| 29行目: | 32行目: | ||
} | } | ||
return [ ...new Set( | return [ ...new Set( | ||
ids | ids | ||
| 42行目: | 41行目: | ||
} catch ( e ) { | } catch ( e ) { | ||
return []; | return []; | ||
} | } | ||
} | } | ||
/** | /** | ||
| 50行目: | 52行目: | ||
*/ | */ | ||
function saveCompareStalls( ids ) { | function saveCompareStalls( ids ) { | ||
mw.storage.set( | mw.storage.set( | ||
STORAGE_KEY, | STORAGE_KEY, | ||
JSON.stringify( ids ) | JSON.stringify( ids ) | ||
); | ); | ||
} | } | ||
/** | /** | ||
| 60行目: | 65行目: | ||
*/ | */ | ||
function showMessage( control, message, isError ) { | function showMessage( control, message, isError ) { | ||
const messageElement = | const messageElement = | ||
control.querySelector( '.stall-compare-message' ); | control.querySelector( | ||
'.stall-compare-message' | |||
); | |||
if ( !messageElement ) { | if ( !messageElement ) { | ||
| 73行目: | 81行目: | ||
Boolean( isError ) | Boolean( isError ) | ||
); | ); | ||
} | } | ||
/** | /** | ||
* | * 比較ボタン生成 | ||
*/ | |||
function createCompareButtons() { | |||
document | |||
.querySelectorAll( | |||
'.stall-compare-placeholder' | |||
) | |||
.forEach( function ( placeholder ) { | |||
/* | |||
* すでに生成済みなら何もしない | |||
*/ | |||
if ( | |||
placeholder.dataset.initialized === '1' | |||
) { | |||
return; | |||
} | |||
/* | |||
* id: | |||
* stall-compare-placeholder-1 | |||
* ↓ | |||
* stallId = "1" | |||
*/ | |||
const prefix = | |||
'stall-compare-placeholder-'; | |||
if ( | |||
!placeholder.id.startsWith( prefix ) | |||
) { | |||
return; | |||
} | |||
const stallId = | |||
placeholder.id.substring( | |||
prefix.length | |||
); | |||
if ( !stallId ) { | |||
return; | |||
} | |||
/* | |||
* JavaScript側で本物のbuttonを作る | |||
*/ | |||
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'; | |||
} ); | |||
} | |||
/** | |||
* ボタン状態更新 | |||
*/ | */ | ||
function updateCompareButtons() { | function updateCompareButtons() { | ||
const ids = getCompareStalls(); | |||
const ids = | |||
getCompareStalls(); | |||
document | document | ||
.querySelectorAll( '.stall-compare-button' ) | .querySelectorAll( | ||
'.stall-compare-button' | |||
) | |||
.forEach( function ( button ) { | .forEach( function ( button ) { | ||
const stallId = | const stallId = | ||
String( button.dataset.stallId || '' ); | String( | ||
button.dataset.stallId || '' | |||
); | |||
const selected = | const selected = | ||
| 92行目: | 184行目: | ||
if ( selected ) { | if ( selected ) { | ||
button.textContent = '比較から外す'; | |||
button.textContent = | |||
'比較から外す'; | |||
button.classList.add( | button.classList.add( | ||
'stall-compare-button-selected' | 'stall-compare-button-selected' | ||
); | ); | ||
button.setAttribute( | button.setAttribute( | ||
'aria-pressed', | 'aria-pressed', | ||
'true' | 'true' | ||
); | ); | ||
} else { | } else { | ||
button.textContent = '比較に追加'; | |||
button.textContent = | |||
'比較に追加'; | |||
button.classList.remove( | button.classList.remove( | ||
'stall-compare-button-selected' | 'stall-compare-button-selected' | ||
); | ); | ||
button.setAttribute( | button.setAttribute( | ||
'aria-pressed', | 'aria-pressed', | ||
'false' | 'false' | ||
); | ); | ||
} | } | ||
} ); | } ); | ||
} | } | ||
/** | /** | ||
* | * 比較ボタンクリック | ||
*/ | */ | ||
document.addEventListener( 'click', function ( event ) { | document.addEventListener( | ||
'click', | |||
function ( event ) { | |||
const button = | |||
event.target.closest( | |||
'.stall-compare-button' | |||
); | |||
if ( !button ) { | |||
return; | |||
} | |||
event.preventDefault(); | |||
const stallId = | |||
String( | |||
button.dataset.stallId || '' | |||
); | |||
const control = | |||
button.closest( | |||
'.stall-compare-control' | |||
); | |||
if ( !stallId ) { | |||
if ( control ) { | |||
showMessage( | |||
control, | |||
'屋台IDを取得できませんでした。', | |||
true | |||
); | |||
} | |||
return; | |||
} | |||
let ids = | |||
getCompareStalls(); | |||
const index = | |||
ids.indexOf( stallId ); | |||
/* | |||
* 登録済み | |||
* → 比較候補から外す | |||
*/ | |||
if ( index !== -1 ) { | |||
ids.splice( | |||
index, | |||
1 | |||
); | |||
saveCompareStalls( | |||
ids | |||
); | ); | ||
updateCompareButtons(); | |||
if ( control ) { | |||
showMessage( | |||
control, | |||
'比較候補から外しました。', | |||
false | |||
); | |||
} | |||
return; | |||
} | } | ||
/* | |||
* 最大4店舗 | |||
*/ | |||
if ( | |||
ids.length >= MAX_COMPARE | |||
) { | |||
if ( control ) { | |||
showMessage( | |||
control, | |||
'比較できる屋台は最大4店舗です。', | |||
true | |||
); | |||
} | |||
return; | |||
} | |||
/* | |||
* 比較候補へ追加 | |||
*/ | |||
ids.push( | |||
stallId | |||
); | |||
ids | saveCompareStalls( | ||
ids | |||
); | |||
updateCompareButtons(); | updateCompareButtons(); | ||
if ( control ) { | if ( control ) { | ||
showMessage( | showMessage( | ||
control, | control, | ||
' | '比較候補に追加しました(' + | ||
ids.length + | |||
'/4)。', | |||
false | false | ||
); | ); | ||
} | } | ||
} | } | ||
); | |||
/** | |||
* 初期化 | |||
*/ | |||
function initStallCompare() { | |||
createCompareButtons(); | |||
updateCompareButtons(); | updateCompareButtons(); | ||
} | |||
/* | /* | ||
* | * 通常ページ | ||
*/ | */ | ||
initStallCompare(); | |||
/* | /* | ||
* | * MediaWiki本文再描画時 | ||
*/ | */ | ||
mw.hook( 'wikipage.content' ).add( function () { | mw.hook( | ||
'wikipage.content' | |||
).add( | |||
function () { | |||
initStallCompare(); | |||
} | |||
); | |||
} ); | } ); | ||
2026年8月11日 (火) 02:26時点における版
/* ========================================
* 屋台比較
* 最大4店舗を localStorage に保存
* Template側にはbuttonを書かず、
* JavaScriptでbuttonを生成する
* ======================================== */
mw.loader.using( 'mediawiki.storage' ).then( function () {
'use strict';
const STORAGE_KEY = 'matsuriWikiCompareStalls';
const MAX_COMPARE = 4;
/**
* 比較候補を取得
*/
function getCompareStalls() {
const raw = mw.storage.get( STORAGE_KEY );
if ( !raw ) {
return [];
}
try {
const ids = JSON.parse( raw );
if ( !Array.isArray( ids ) ) {
return [];
}
return [ ...new Set(
ids
.map( String )
.filter( function ( id ) {
return id !== '';
} )
) ].slice( 0, MAX_COMPARE );
} catch ( e ) {
return [];
}
}
/**
* 比較候補を保存
*/
function saveCompareStalls( ids ) {
mw.storage.set(
STORAGE_KEY,
JSON.stringify( ids )
);
}
/**
* メッセージ表示
*/
function showMessage( control, message, isError ) {
const messageElement =
control.querySelector(
'.stall-compare-message'
);
if ( !messageElement ) {
return;
}
messageElement.textContent = message;
messageElement.classList.toggle(
'stall-compare-message-error',
Boolean( isError )
);
}
/**
* 比較ボタン生成
*/
function createCompareButtons() {
document
.querySelectorAll(
'.stall-compare-placeholder'
)
.forEach( function ( placeholder ) {
/*
* すでに生成済みなら何もしない
*/
if (
placeholder.dataset.initialized === '1'
) {
return;
}
/*
* id:
* stall-compare-placeholder-1
* ↓
* stallId = "1"
*/
const prefix =
'stall-compare-placeholder-';
if (
!placeholder.id.startsWith( prefix )
) {
return;
}
const stallId =
placeholder.id.substring(
prefix.length
);
if ( !stallId ) {
return;
}
/*
* JavaScript側で本物のbuttonを作る
*/
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';
} );
}
/**
* ボタン状態更新
*/
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'
);
}
} );
}
/**
* 比較ボタンクリック
*/
document.addEventListener(
'click',
function ( event ) {
const button =
event.target.closest(
'.stall-compare-button'
);
if ( !button ) {
return;
}
event.preventDefault();
const stallId =
String(
button.dataset.stallId || ''
);
const control =
button.closest(
'.stall-compare-control'
);
if ( !stallId ) {
if ( control ) {
showMessage(
control,
'屋台IDを取得できませんでした。',
true
);
}
return;
}
let ids =
getCompareStalls();
const index =
ids.indexOf( stallId );
/*
* 登録済み
* → 比較候補から外す
*/
if ( index !== -1 ) {
ids.splice(
index,
1
);
saveCompareStalls(
ids
);
updateCompareButtons();
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();
if ( control ) {
showMessage(
control,
'比較候補に追加しました(' +
ids.length +
'/4)。',
false
);
}
}
);
/**
* 初期化
*/
function initStallCompare() {
createCompareButtons();
updateCompareButtons();
}
/*
* 通常ページ
*/
initStallCompare();
/*
* MediaWiki本文再描画時
*/
mw.hook(
'wikipage.content'
).add(
function () {
initStallCompare();
}
);
} );