「Widget:Custom map」の版間の差分
提供:ロマサガ2リメイク 攻略Wiki(ロマンシング サガ2 リベンジオブザセブン)
編集の要約なし |
編集の要約なし |
||
1行目: | 1行目: | ||
<includeonly> | <includeonly> | ||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> | |||
<script src="https://dq.h1g.jp/leaflet/leaflet.js" ></script> | |||
<script src="https://dq.h1g.jp/img/dq10_offline/map/js/leaflet-easy-button.js"></script> | |||
<script src="https://dq.h1g.jp/img/dq10_offline/map/js/leaflet-tag-filter-button.js"></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script> | |||
<script src="https://dq.h1g.jp/leaflet/custom-leaflet-draw-locale.js" ></script> | |||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.7.2/lightgallery.min.js"></script> | |||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"/> | |||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/> | |||
<link rel="stylesheet" href="https://dq.h1g.jp/img/dq10_offline/map/css/leaflet-easy-button.css" /> | |||
<link rel="stylesheet" href="https://dq.h1g.jp/img/dq10_offline/map/css/leaflet-tag-filter-button.css" /> | |||
<link rel="stylesheet" href="https://dq.h1g.jp/img/marker-icon-img/editable-popup.css" /> | |||
<link rel="stylesheet" href="https://dq.h1g.jp/img/lightgallery.min.css"/> | |||
<script type="text/javascript"> | |||
let map; | |||
var drawnItems; | |||
var drawControl; | |||
var editMode = false; | |||
// APIベースURLの設定を動的に行い、一度設定したら変更できないようにする | |||
(function() { | |||
if (typeof window.API_BASE_URL === 'undefined') { | |||
const currentUrl = new URL(window.location.href); | |||
const baseUrl = currentUrl.origin + currentUrl.pathname.split('/').slice(0, -1).join('/'); | |||
Object.defineProperty(window, 'API_BASE_URL', { | |||
value: baseUrl, | |||
writable: false, | |||
configurable: false, | |||
enumerable: true | |||
}); | |||
console.log('API_BASE_URL set to:', baseUrl); | |||
// 初期化完了フラグを設定 | |||
window.API_INITIALIZED = true; | |||
} | |||
})(); | |||
// APIコールを行う前に必ずURLを検証 | |||
function validateAndCallApi(endpoint, options) { | |||
const currentUrl = new URL(window.location.href); | |||
const expectedDomain = currentUrl.origin; | |||
if (!window.API_BASE_URL.startsWith(expectedDomain)) { | |||
console.error('API URL mismatch detected'); | |||
return Promise.reject(new Error('Invalid API domain')); | |||
} | |||
// | // APIコールを実行 | ||
return $.ajax({ | |||
url: window.API_BASE_URL + endpoint, | |||
...options | |||
}); | }); | ||
} | |||
window.savePopupContent = savePopupContent; | |||
// 表示する画像 | |||
var imageBase = { | |||
url: '<!--{$img}-->', | |||
width: <!--{$imgwidth}-->, // 画像のサイズ | |||
height: <!--{$imgheight}--> | |||
}; | |||
// 地図初期化 | |||
var imageBounds = L.latLngBounds( | |||
[0, 0], | |||
[imageBase.height, imageBase.width], | |||
); | |||
map = L.map('map', { | |||
crs: L.CRS.Simple, | |||
maxBounds: imageBounds.pad(0.5), | |||
minZoom: <!--{$minzoom}-->, // minZoomを0に設定 | |||
maxZoom: <!--{$maxzoom}-->, | |||
}); | |||
map.fitBounds(imageBounds); | |||
L.imageOverlay(imageBase.url, imageBounds,{ | |||
attribution: '<a href="https://h1g.jp/" target="_blank">【ヘイグ】</a>' | |||
}).addTo(map); | |||
var drawnItems = new L.FeatureGroup(); | |||
map.addLayer(drawnItems); | |||
var | // Leaflet.Draw | ||
var drawnItems = new L.FeatureGroup(); | |||
map.addLayer(drawnItems); | |||
drawControl = new L.Control.Draw({ | |||
edit: { | |||
featureGroup: drawnItems, | |||
poly: { | |||
allowIntersection: false | |||
} | |||
}, | |||
draw: { | |||
polygon: false, | |||
polyline: false, | |||
rectangle: false, | |||
circle: true, | |||
marker: true, | |||
circlemarker: false | |||
} | |||
}); | |||
map.addControl(drawControl); | |||
// 初期状態では編集モードを無効にする | |||
drawControl.remove(); | |||
map.on(L.Draw.Event.CREATED, function (event) { | |||
var layer = event.layer; | |||
if (layer instanceof L.Marker) { | |||
layer.setIcon(iconDefinitions.icon1); | |||
layer.options.popupData = {title: '', content: '', iconType: 'icon1'}; | |||
var popupContent = | |||
'<div>' + | |||
'<textarea id="popup-title" cols="30" rows="1" placeholder="タイトル"></textarea>' + | |||
'<textarea id="popup-content" cols="30" rows="2" placeholder="内容"></textarea>' + | |||
'<select id="icon-type">' + | |||
'<option value="icon1" selected>アイコン1</option>' + | |||
'<option value="icon2">アイコン2</option>' + | |||
'<option value="icon3">アイコン3</option>' + | |||
'<option value="icon4">アイコン4</option>' + | |||
'<option value="icon5">アイコン5</option>' + | |||
'<option value="icon6">アイコン6</option>' + | |||
'</select>' + | |||
'<button onclick="savePopupContent(this)">保存</button>' + | |||
'</div>'; | |||
layer.bindPopup(popupContent); | |||
} else { | |||
layer.bindPopup('No description'); | |||
} | |||
createEditablePopup(layer); | |||
drawnItems.addLayer(layer); | |||
if (layer instanceof L.Marker) { | |||
layer.openPopup(); | |||
} | |||
}); | |||
//////////////////権限を確認 | |||
function checkUserGroup(group, callback) { | |||
$.ajax({ | |||
url: API_BASE_URL + '/api.php', | |||
data: { | |||
action: 'query', | |||
meta: 'userinfo', | |||
uiprop: 'groups', | |||
format: 'json' | |||
}, | |||
type: 'GET', | |||
success: function(data) { | |||
var userGroups = data.query.userinfo.groups; | |||
var isInGroup = userGroups.indexOf(group) !== -1; | |||
callback(isInGroup); | |||
}, | }, | ||
error: function() { | |||
console.error('Failed to check user group'); | |||
callback(false); | |||
} | } | ||
}); | }); | ||
} | |||
////////////////////////////////////////////////////////////// | |||
// | // アイコンの定義 | ||
var iconDefinitions = { | |||
icon1: L.icon({ | |||
iconUrl: '<!--{$icon1|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-blue.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
if ( | iconSize: [25, 41], | ||
layer. | iconAnchor: [12, 41], | ||
layer. | popupAnchor: [1, -41], | ||
var | shadowSize: [41, 41] | ||
}), | |||
icon2: L.icon({ | |||
iconUrl: '<!--{$icon2|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-red.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
iconSize: [25, 41], | |||
iconAnchor: [12, 41], | |||
popupAnchor: [1, -41], | |||
shadowSize: [41, 41] | |||
}), | |||
icon3: L.icon({ | |||
iconUrl: '<!--{$icon3|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-violet.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
iconSize: [25, 41], | |||
iconAnchor: [12, 41], | |||
popupAnchor: [1, -41], | |||
shadowSize: [41, 41] | |||
}), | |||
icon4: L.icon({ | |||
iconUrl: '<!--{$icon4|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-green.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
iconSize: [25, 41], | |||
iconAnchor: [12, 41], | |||
popupAnchor: [1, -41], | |||
shadowSize: [41, 41] | |||
}), | |||
icon5: L.icon({ | |||
iconUrl: '<!--{$icon5|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-gold.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
iconSize: [25, 41], | |||
iconAnchor: [12, 41], | |||
popupAnchor: [1, -41], | |||
shadowSize: [41, 41] | |||
}), | |||
icon6: L.icon({ | |||
iconUrl: '<!--{$icon6|default:'https://dq.h1g.jp/img/marker-icon-img/marker-icon-black.png'}-->', | |||
shadowUrl: 'https://dq.h1g.jp/img/marker-icon-img/marker-shadow.png', | |||
iconSize: [25, 41], | |||
iconAnchor: [12, 41], | |||
popupAnchor: [1, -41], | |||
shadowSize: [41, 41] | |||
}) | |||
}; | |||
// ポップアップの作成 | |||
function createEditablePopup(layer) { | |||
var popupData = layer.options.popupData || {}; | |||
var title = popupData.title || ''; | |||
var content = popupData.content || ''; | |||
var iconType = popupData.iconType || 'icon1'; | |||
layer.on('popupopen', function() { | |||
var popup = this.getPopup(); | |||
var popupData = this.options.popupData || {}; | |||
var title = popupData.title || ''; | |||
var content = popupData.content || ''; | |||
var iconType = popupData.iconType || 'icon1'; | |||
// タイトルと内容が両方空の場合、ポップアップを表示しない | |||
if (!editMode && title === '' && content === '') { | |||
layer.closePopup(); | |||
// カーソルをデフォルトに設定 | |||
layer._icon.style.cursor = 'default'; | |||
return; | |||
} | |||
if (editMode) { // 編集モードの場合のみtextareaを表示 | |||
var iconSelector = ''; | |||
if (layer instanceof L.Marker) { | |||
iconSelector = '<select id="icon-type">' + | |||
'<option value="icon1"' + (iconType === 'icon1' ? ' selected' : '') + '>アイコン1</option>' + | |||
'<option value="icon2"' + (iconType === 'icon2' ? ' selected' : '') + '>アイコン2</option>' + | |||
'<option value="icon3"' + (iconType === 'icon3' ? ' selected' : '') + '>アイコン3</option>' + | |||
'<option value="icon4"' + (iconType === 'icon4' ? ' selected' : '') + '>アイコン4</option>' + | |||
'<option value="icon5"' + (iconType === 'icon5' ? ' selected' : '') + '>アイコン5</option>' + | |||
'<option value="icon6"' + (iconType === 'icon6' ? ' selected' : '') + '>アイコン6</option>' + | |||
'</select>'; | |||
} | |||
var editableContent = | |||
'<div>' + | '<div>' + | ||
'<textarea id="popup-title" cols=" | '<textarea id="popup-title" cols="60" rows="3" placeholder="タイトル">' + title + '</textarea>' + | ||
'<textarea id="popup-content" cols=" | '<textarea id="popup-content" cols="60" rows="10" placeholder="内容">' + content + '</textarea>' + | ||
iconSelector + | |||
'<button onclick="savePopupContent(this)">保存</button>' + | '<button onclick="savePopupContent(this)">保存</button>' + | ||
'</div>'; | '</div>'; | ||
popup.setContent(editableContent); | |||
} else { | } else { | ||
layer. | // 編集モードでない場合は表示用のコンテンツを設定 | ||
if (title === '' && content === '') { | |||
layer.unbindPopup(); // タイトルと内容が空の場合はポップアップを表示しない | |||
} else { | |||
var displayContent = '<strong>' + title + '</strong><br>' + content; | |||
var renderedContent = renderMediaWikiContent(displayContent); | |||
popup.setContent(renderedContent); | |||
} | |||
} | } | ||
}); | }); | ||
// | // 既にポップアップが設定されている場合に備え、再設定 | ||
if (!(title === '' && content === '')) { | |||
if ( | var displayContent = '<strong>' + title + '</strong><br>' + content; | ||
var renderedContent = renderMediaWikiContent(displayContent); | |||
layer.bindPopup(renderedContent); | |||
} | } | ||
} | } | ||
function | |||
// MediaWikiコンテンツのレンダリング | |||
function renderMediaWikiContent(content) { | |||
var renderedContent = ''; | |||
$.ajax({ | |||
url: API_BASE_URL + '/api.php', | |||
data: { | |||
action: 'parse', | |||
text: content, | |||
format: 'json' | |||
}, | |||
async: false, | |||
success: function(data) { | |||
renderedContent = data.parse.text['*']; | |||
// レンダリング後の画像処理 | |||
setTimeout(function() { | |||
initLightGallery(renderedContent); | |||
}, 100); | |||
}, | |||
error: function() { | |||
console.error('Failed to render MediaWiki content'); | |||
} | } | ||
}); | }); | ||
return renderedContent; | |||
} | } | ||
function | |||
// LightGalleryの初期化を修正 | |||
function initLightGallery(content) { | |||
const popupContent = $('.leaflet-popup-content'); | |||
// | // 既存のLightGalleryインスタンスを破棄 | ||
if (popupContent.data('lightGallery')) { | |||
popupContent.data('lightGallery').destroy(true); | |||
} | } | ||
// | // 既存のlg-containerを削除 | ||
$('.lg-container').remove(); | |||
popupContent.find('img').each(function() { | |||
const img = $(this); | |||
// gallery-itemクラスを持つdivで既に囲まれていない場合のみ処理 | |||
if (!img.parent().hasClass('gallery-item')) { | |||
let fullSizeUrl = convertToFullSize(img.attr('src')); | |||
img.wrap('<div class="gallery-item" data-src="' + fullSizeUrl + '"></div>'); | |||
} | |||
}); | |||
// LightGalleryを初期化(ナビゲーション矢印を非表示に) | |||
if (!popupContent.data('lightGallery')) { | |||
lightGallery(popupContent[0], { | |||
selector: '.gallery-item', | |||
plugins: [], | |||
speed: 500, | |||
download: false, | |||
counter: false, | |||
enableDrag: false, | |||
enableTouch: false, | |||
hideControlOnEnd: true, | |||
controls: false, | |||
prevHtml: '', | |||
nextHtml: '', | |||
backdropDuration: 300, // 背景のフェード時間 | |||
}); | }); | ||
} | } | ||
} | |||
// ポップアップが閉じられたときの処理を追加 | |||
map.on('popupclose', function(e) { | |||
const popupContent = $(e.popup.getContent()); | |||
if (popupContent.data('lightGallery')) { | |||
popupContent.data('lightGallery').destroy(true); | |||
} | } | ||
}); | |||
// MediaWikiの画像URLを最大サイズに変換する関数 | |||
function convertToFullSize(url) { | |||
// MediaWikiのサムネイルURLをパースして最大サイズのURLに変換 | |||
if (url.includes('/thumb/')) { | |||
// /thumb/を除去し、最後の/以降を削除して元のファイル名に戻す | |||
return url.replace('/thumb/', '/') | |||
.replace(/\/\d+px-[^/]+$/, ''); | |||
// | |||
} | } | ||
return url; | |||
} | |||
// ポップアップが開かれたときのイベントを追加 | |||
map.on('popupopen', function(e) { | |||
if ( | setTimeout(function() { | ||
initLightGallery(e.popup.getContent()); | |||
}, 100); | |||
}); | |||
// マップのクリーンアップ処理も追加 | |||
map.on('unload', function() { | |||
$('.lg-container').remove(); | |||
$('.leaflet-popup-content').each(function() { | |||
if ($(this).data('lightGallery')) { | |||
$(this).data('lightGallery').destroy(true); | |||
} | } | ||
}); | }); | ||
}); | |||
/// スタイルを更新 | |||
const styles = ` | |||
<style> | |||
.gallery-item { | |||
cursor: pointer; | |||
display: inline-block; | |||
} | |||
.gallery-item img { | |||
max-width: 200px; | |||
height: auto; | |||
transition: transform 0.3s ease; | |||
} | |||
.gallery-item:hover img { | |||
transform: scale(1.05); | |||
} | |||
.lg-img-wrap { | |||
text-align: center; | |||
} | |||
.lg-img-wrap img { | |||
max-height: 90vh !important; | |||
max-width: 90vw !important; | |||
object-fit: contain; | |||
} | |||
/* ナビゲーション矢印を非表示 */ | |||
.lg-next, .lg-prev { | |||
display: none !important; | |||
} | |||
</style> | |||
`; | |||
// ポップアップの内容を保存 | |||
function savePopupContent(button) { | |||
var popup = button.closest('.leaflet-popup'); | |||
var content = popup.querySelector('.leaflet-popup-content'); | |||
var title = content.querySelector('#popup-title').value; | |||
var text = content.querySelector('#popup-content').value; | |||
var iconType = content.querySelector('#icon-type') ? content.querySelector('#icon-type').value : null; | |||
var layer = drawnItems.getLayers().find(function(layer) { | |||
return layer.getPopup() && layer.getPopup().getElement() === popup; | |||
}); | |||
/ | if (layer) { | ||
var savedContent = '<strong>' + title + '</strong><br>' + text; | |||
layer.setPopupContent(savedContent); | |||
layer.options.popupData = {title: title, content: text, iconType: iconType}; | |||
// ポップアップを更新した後にLightGalleryを再初期化 | |||
setTimeout(function() { | setTimeout(function() { | ||
initLightGallery( | initLightGallery(savedContent); | ||
}, 100); | }, 100); | ||
layer.closePopup(); | |||
layer.openPopup(); | |||
} | |||
} | |||
// 編集イベントのリスナーも追加 | |||
map.on(L.Draw.Event.EDITED, function (event) { | |||
var layers = event.layers; | |||
layers.eachLayer(function (layer) { | |||
drawnItems.addLayer(layer); | |||
}); | }); | ||
}); | |||
// | // 削除イベントのリスナーも追加 | ||
map | map.on(L.Draw.Event.DELETED, function (event) { | ||
var layers = event.layers; | |||
layers.eachLayer(function (layer) { | |||
drawnItems.removeLayer(layer); | |||
}); | |||
}); | |||
// 編集ボタン | |||
var editButton = L.easyButton({ | |||
states: [{ | |||
stateName: 'enable-edit', | |||
icon: '<img src="https://dq.h1g.jp/img/marker-icon-img/edit-solid.svg">', | |||
title: 'マップを編集する', | |||
onClick: function(btn, map) { | |||
// checkUserGroup('map-edit-member', function(isInGroup) { | |||
checkUserGroup('sysop', function(isInGroup) { | |||
// checkUserGroup('internal-staff', function(isInGroup) { | |||
if (isInGroup) { | |||
editMode = true; | |||
map.addControl(drawControl); | |||
btn.state('disable-edit'); | |||
updateAllPopups(); | |||
saveButton.addTo(map); // saveButtonを表示する | |||
} else { | |||
// alert('あなたには編集権限がありません。\n攻略に参加することでマップが編集できるようになります。'); | |||
alert('あなたには編集権限がありません。'); | |||
} | |||
}); | |||
} | |||
}, { | |||
stateName: 'disable-edit', | |||
icon: '<img src="https://dq.h1g.jp/img/marker-icon-img/edit-solid.svg">', | |||
title: '編集の終了', | |||
onClick: function(btn, map) { | |||
editMode = false; | |||
drawControl.remove(); | |||
btn.state('enable-edit'); | |||
updateAllPopups(); | |||
saveButton.remove(); // saveButtonを非表示にする | |||
} | |||
}] | |||
}).addTo(map); | |||
function updateAllPopups() { | |||
drawnItems.eachLayer(function(layer) { | |||
if (layer.getPopup()) { | |||
layer.closePopup(); | |||
if (editMode) { | |||
createEditablePopup(layer); | |||
layer.openPopup(); | |||
} else if (!(layer.options.popupData && layer.options.popupData.title === '' && layer.options.popupData.content === '')) { | |||
layer.bindPopup(layer.options.popupData.title + "<br>" + layer.options.popupData.content); | |||
} | } | ||
} | } | ||
}); | }); | ||
} | |||
// 保存ボタン | |||
var saveButton = L.easyButton('<img src="https://dq.h1g.jp/img/marker-icon-img/save-solid.svg">', function() { | |||
if (editMode) { | |||
var geoJSONData = convertToGeoJSON(drawnItems); | |||
if (geoJSONData) { | |||
// console.log(geoJSONData); | |||
saveToWikiPage(geoJSONData); | |||
saveButton.remove(); // saveButtonを非表示にする | |||
} else { | |||
alert('Error: Invalid GeoJSON data'); | |||
var | |||
} | |||
} | } | ||
} else { | |||
alert('Please enable edit mode before saving.'); | |||
} | } | ||
}, '変更を保存'); | |||
function loadPinsFromWikiPage() { | |||
getMwToken() | |||
.then(function(token) { | |||
return validateAndCallApi('/api.php', { | |||
type: "GET", | |||
data: { | |||
action: 'query', | |||
titles: "<!--{$geojson}-->", | |||
prop: 'revisions', | |||
rvprop: 'content', | |||
format: 'json', | |||
redirects: 0 | |||
}, | |||
headers: { | |||
'Authorization': 'Bearer ' + token | |||
} | } | ||
}); | |||
}) | |||
.then(function(response) { | |||
try { | |||
// レスポンスからページコンテンツを取得 | |||
const pages = response.query.pages; | |||
const pageId = Object.keys(pages)[0]; | |||
const content = pages[pageId].revisions[0]['*']; | |||
// GeoJSONデータを抽出して処理 | |||
const geoJSONData = JSON.parse(content); | |||
processGeoJSONData(geoJSONData); | |||
} catch (error) { | |||
console.error("Error processing data:", error); | |||
throw error; | |||
} | } | ||
}) | |||
.catch(function(error) { | |||
console.error("Error loading data:", error); | |||
// より詳細なエラー情報を表示 | |||
console.error('Detailed error:', { | |||
message: error.message, | |||
status: error.status, | |||
responseText: error.responseText | |||
}); | |||
}); | }); | ||
} | |||
function processGeoJSONData(geoJSON) { | |||
drawnItems.clearLayers(); | |||
var | |||
if ( | L.geoJSON(geoJSON, { | ||
pointToLayer: function (feature, latlng) { | |||
var iconType = feature.properties.iconType || 'icon1'; | |||
var icon = iconDefinitions[iconType] || iconDefinitions.icon1; | |||
if (feature.properties.radius) { | |||
return L.circle(latlng, { | |||
radius: feature.properties.radius, | |||
tags: feature.properties.tags | |||
}); | |||
} else { | } else { | ||
return L.marker(latlng, { | |||
icon: icon, | |||
tags: feature.properties.tags | |||
}); | |||
} | } | ||
}, | |||
onEachFeature: function (feature, layer) { | |||
var popupContent = '<strong>' + feature.properties.title + '</strong><br>' + feature.properties.content; | |||
layer.bindPopup(popupContent); | |||
layer.options.popupData = { | |||
title: feature.properties.title, | |||
content: feature.properties.content, | |||
iconType: feature.properties.iconType, | |||
tags: feature.properties.tags | |||
}; | |||
createEditablePopup(layer); | |||
drawnItems.addLayer(layer); | |||
} | |||
}); | |||
} | |||
////////////////////////////////////////////////////////////////////////////// | |||
function convertToGeoJSON(drawnItems) { | |||
var geoJSON = { | |||
"type": "FeatureCollection", | |||
"features": [] | |||
}; | |||
drawnItems.eachLayer(function(layer) { | |||
var feature = layer.toGeoJSON(); | |||
var popupData = layer.options.popupData || {}; | |||
feature.properties = { | |||
title: popupData.title || '', | |||
content: popupData.content || '', | |||
iconType: popupData.iconType || 'icon1', | |||
tags: popupData.tags || [getTagFromIconType(popupData.iconType || 'icon1')] | |||
}; | |||
if (layer instanceof L.Circle) { | |||
feature.properties.radius = layer.getRadius(); | |||
feature.geometry = { | |||
type: "Point", | |||
coordinates: [layer.getLatLng().lng, layer.getLatLng().lat] | |||
}; | |||
} else if (layer instanceof L.Marker) { | |||
feature.geometry = { | |||
type: "Point", | |||
coordinates: [layer.getLatLng().lng, layer.getLatLng().lat] | |||
}; | |||
} else { | } else { | ||
// その他のタイプのレイヤーは無視する | |||
return; | |||
} | } | ||
} | |||
geoJSON.features.push(feature); | |||
}); | |||
// GeoJSONデータの有効性をチェック | |||
try { | |||
JSON.parse(JSON.stringify(geoJSON)); | |||
} catch (error) { | |||
console.error("Invalid GeoJSON data:", error); | |||
return null; | |||
} | |||
return JSON.stringify(geoJSON, null, 2); // 整形されたJSONを返す | |||
} | |||
function getTagFromIconType(iconType) { | |||
switch (iconType) { | |||
case 'icon1': | |||
return '<!--{$filter1|default:"分類1"}-->'; | |||
case 'icon2': | |||
return '<!--{$filter2|default:"分類2"}-->'; | |||
case 'icon3': | |||
return '<!--{$filter3|default:"分類3"}-->'; | |||
case 'icon4': | |||
return '<!--{$filter4|default:"分類4"}-->'; | |||
case 'icon5': | |||
return '<!--{$filter5|default:"分類5"}-->'; | |||
case 'icon6': | |||
return '<!--{$filter6|default:"分類6"}-->'; | |||
default: | |||
return ''; | |||
} | |||
} | |||
// フィルタボタンの定義 | |||
// Leaflet.Control.TagFilterButton の設定 | |||
L.control.tagFilterButton({ | |||
data: [ | |||
'<!--{$filter1|default:"分類1"}-->', | |||
'<!--{$filter2|default:"分類2"}-->', | |||
'<!--{$filter3|default:"分類3"}-->', | |||
'<!--{$filter4|default:"分類4"}-->', | |||
'<!--{$filter5|default:"分類5"}-->', | |||
'<!--{$filter6|default:"分類6"}-->' | |||
], | |||
icon: '<img src="https://dq.h1g.jp/img/marker-icon-img/filter.png">', | |||
filterOnEveryClick: true | |||
}).addTo(map); | |||
// トークンを取得する関数の前に追加 | |||
function validateToken(token) { | |||
if (!token || typeof token !== 'string') { | |||
console.warn('Invalid token format'); | |||
return false; | |||
} | } | ||
return true; | |||
} | |||
function getMwToken() { | |||
return new Promise((resolve, reject) => { | |||
const apiUrl = API_BASE_URL + '/api.php'; | |||
console.log('Requesting token from:', apiUrl); // APIリクエストURLの確認 | |||
$.ajax({ | |||
url: apiUrl, | |||
data: { | |||
action: 'query', | |||
meta: 'tokens', | |||
type: 'csrf', | |||
format: 'json' | |||
}, | |||
type: 'GET', | |||
success: function(response) { | |||
console.log('Token response:', response); // レスポンスの確認 | |||
if ( | if (response.query && response.query.tokens && response.query.tokens.csrftoken) { | ||
const token = response.query.tokens.csrftoken; | |||
// トークンの検証を一時的に緩和 | |||
resolve(token); | |||
} else { | } else { | ||
console.error('Invalid token response structure:', response); | |||
reject(new Error('Failed to get token')); | |||
} | } | ||
}, | }, | ||
error: function(xhr, status, error) { | |||
console.error('Token request failed:', { | |||
status: status, | |||
error: error, | |||
response: xhr.responseText | |||
}); | |||
reject(error); | |||
} | |||
} | } | ||
}); | }); | ||
} | }); | ||
} | |||
function saveToWikiPage(geoJSONData) { | |||
var formattedData = geoJSONData; | |||
// 保存前に新しいトークンを取得 | |||
getMwToken() | |||
.then(function(token) { | |||
return $.ajax({ | |||
type: "POST", | |||
url: API_BASE_URL + '/api.php', | url: API_BASE_URL + '/api.php', | ||
data: { | data: { | ||
action: ' | action: 'edit', | ||
title: '<!--{$geojson}-->', | |||
text: formattedData, | |||
token: token, | |||
format: 'json' | format: 'json' | ||
} | } | ||
}); | }); | ||
}) | |||
.then(function(response) { | |||
if (response.edit && response.edit.result === 'Success') { | |||
alert('データは正常に保存されました'); | |||
editMode = false; | |||
drawControl.remove(); | |||
editButton.state('enable-edit'); | |||
saveButton.remove(); | |||
// 保存成功後、新しいトークンで再読み込み | |||
getMwToken() | |||
.then(function(newToken) { | |||
loadPinsFromWikiPage(newToken); | |||
}) | |||
.catch(function(error) { | |||
console.error("Failed to get token for reload:", error); | |||
}); | |||
} else { | |||
throw new Error('保存に失敗しました'); | |||
} | |||
}) | |||
.catch(function(error) { | |||
console.error("Save error:", error); | |||
alert('データの保存に失敗しました: ' + error.message); | |||
}); | }); | ||
} | |||
map.on(L.Draw.Event.CREATED, function (event) { | |||
var layer = event.layer; | |||
if (layer instanceof L.Marker) { | |||
layer.setIcon(iconDefinitions.icon1); | |||
layer.options.popupData = {title: '', content: '', iconType: 'icon1'}; | |||
} | } | ||
createEditablePopup(layer); | |||
drawnItems.addLayer(layer); | |||
}); | |||
//////////////////////////////////////////////////// | |||
// ページ読み込み時にデータを読み込む | |||
$(document).ready(function() { | |||
setTimeout(function() { | setTimeout(function() { | ||
map.invalidateSize(); | map.invalidateSize(); | ||
// 初期読み込み時にトークンを取得してからデータを読み込む | |||
getMwToken() | getMwToken() | ||
.then(function(token) { | .then(function(token) { | ||
console.log('Initial token obtained'); | console.log('Initial token obtained'); | ||
loadPinsFromWikiPage(token); | loadPinsFromWikiPage(token); | ||
}) | }) | ||
.catch(function(error) { | .catch(function(error) { | ||
846行目: | 837行目: | ||
}, 500); | }, 500); | ||
}); | }); | ||
</script> | |||
// エラーハンドリングの改善 | |||
function handleApiError(error) { | |||
console.error('API Error:', error); | |||
if (error.status === 403) { | |||
alert('アクセス権限がありません。再度ログインしてください。'); | |||
} else if (error.status === 404) { | |||
alert('リソースが見つかりません。'); | |||
} else { | |||
alert('エラーが発生しました。ページを更新してください。'); | |||
} | |||
} | |||
</script> | |||
</includeonly> |