No edit summary |
No edit summary |
||
| Line 468: | Line 468: | ||
console.log('Page preview popup initialized'); | console.log('Page preview popup initialized'); | ||
// --- Mobile table scroll fix --- | |||
(function() { | |||
if (window.innerWidth > 768) return; | |||
var tables = document.querySelectorAll('#mw-content-text table'); | |||
for (var i = 0; i < tables.length; i++) { | |||
var table = tables[i]; | |||
if (table.parentElement.classList.contains('table-scroll-wrap')) continue; | |||
var wrapper = document.createElement('div'); | |||
wrapper.className = 'table-scroll-wrap'; | |||
wrapper.style.overflowX = 'auto'; | |||
wrapper.style.webkitOverflowScrolling = 'touch'; | |||
wrapper.style.maxWidth = '100%'; | |||
wrapper.style.display = 'block'; | |||
table.parentNode.insertBefore(wrapper, table); | |||
wrapper.appendChild(table); | |||
} | |||
})(); | |||
})(); | })(); | ||
}); // end $(document).ready | }); // end $(document).ready | ||
Revision as of 00:42, 19 February 2026
1 /* Any JavaScript here will be loaded for all users on every page load. */
2
3 /* ============================================================
4 THEME TOGGLE — Dark/Light mode switcher
5 Defaults to dark. Saves preference to localStorage.
6 ============================================================ */
7 (function() {
8 var saved = localStorage.getItem('flatmmo-theme') || 'dark';
9 document.documentElement.className += ' ' + saved + '-theme';
10
11 var searchMode = localStorage.getItem('flatmmo-search') || 'top';
12 document.documentElement.className += ' search-' + searchMode;
13
14 document.addEventListener('DOMContentLoaded', function() {
15 document.body.classList.add(saved + '-theme');
16 document.body.classList.add('search-' + searchMode);
17 });
18 })();
19
20 $(document).ready(function () {
21 console.log("loaded");
22
23 // --- Theme toggle button ---
24 (function() {
25 var currentTheme = localStorage.getItem('flatmmo-theme') || 'dark';
26
27 document.body.classList.remove('dark-theme', 'light-theme');
28 document.body.classList.add(currentTheme + '-theme');
29
30 var btn = document.createElement('button');
31 btn.className = 'theme-toggle-btn';
32 btn.title = 'Toggle light/dark mode';
33 btn.textContent = currentTheme === 'dark' ? '☀️' : '🌙';
34
35 btn.addEventListener('click', function() {
36 var isDark = document.body.classList.contains('dark-theme');
37 var newTheme = isDark ? 'light' : 'dark';
38
39 document.body.classList.remove('dark-theme', 'light-theme');
40 document.body.classList.add(newTheme + '-theme');
41 document.documentElement.classList.remove('dark-theme', 'light-theme');
42 document.documentElement.classList.add(newTheme + '-theme');
43
44 localStorage.setItem('flatmmo-theme', newTheme);
45 btn.textContent = newTheme === 'dark' ? '☀️' : '🌙';
46 });
47
48 var target = document.querySelector('.cosmos-header__wiki-buttons')
49 || document.querySelector('.cosmos-actions')
50 || document.querySelector('.wds-button-group')
51 || document.querySelector('#content');
52 if (target) {
53 if (target.id === 'content') {
54 target.insertBefore(btn, target.firstChild);
55 } else {
56 target.appendChild(btn);
57 }
58 }
59 })();
60
61 // --- Search bar toggle + compact header search bar ---
62 (function() {
63 var searchMode = localStorage.getItem('flatmmo-search') || 'top';
64
65 document.body.classList.remove('search-top', 'search-header');
66 document.body.classList.add('search-' + searchMode);
67
68 if (searchMode === 'header') {
69 window.scrollTo(0, 0);
70 document.addEventListener('DOMContentLoaded', function() {
71 window.scrollTo(0, 0);
72 });
73 }
74
75 var searchToggle = document.createElement('button');
76 searchToggle.className = 'search-toggle-btn';
77 searchToggle.title = 'Toggle search bar position';
78 searchToggle.textContent = searchMode === 'top' ? '🔍' : '🔎';
79
80 searchToggle.addEventListener('click', function() {
81 var isTop = document.body.classList.contains('search-top');
82 var newMode = isTop ? 'header' : 'top';
83
84 document.body.classList.remove('search-top', 'search-header');
85 document.body.classList.add('search-' + newMode);
86 document.documentElement.classList.remove('search-top', 'search-header');
87 document.documentElement.classList.add('search-' + newMode);
88
89 localStorage.setItem('flatmmo-search', newMode);
90 searchToggle.textContent = newMode === 'top' ? '🔍' : '🔎';
91 });
92
93 var searchWrap = document.createElement('div');
94 searchWrap.className = 'header-search-wrap';
95
96 var searchInput = document.createElement('input');
97 searchInput.type = 'text';
98 searchInput.className = 'header-search-input';
99 searchInput.placeholder = 'Search wiki...';
100 searchInput.setAttribute('autocomplete', 'off');
101
102 var searchBtn = document.createElement('button');
103 searchBtn.className = 'header-search-btn';
104 searchBtn.innerHTML = '🔍';
105 searchBtn.title = 'Search';
106
107 function doSearch() {
108 var q = searchInput.value.trim();
109 if (q) {
110 window.location.href = mw.util.getUrl('Special:Search') + '?search=' + encodeURIComponent(q);
111 }
112 }
113
114 searchBtn.addEventListener('click', doSearch);
115 searchInput.addEventListener('keydown', function(e) {
116 if (e.key === 'Enter') doSearch();
117 });
118
119 searchWrap.appendChild(searchInput);
120 searchWrap.appendChild(searchBtn);
121
122 var target = document.querySelector('.cosmos-header__wiki-buttons')
123 || document.querySelector('.cosmos-actions')
124 || document.querySelector('.wds-button-group')
125 || document.querySelector('#content');
126 if (target && target.id !== 'content') {
127 target.appendChild(searchToggle);
128 target.style.position = 'relative';
129 target.appendChild(searchWrap);
130
131 var bannerUser = document.querySelector('.cosmos-userButton-label')
132 || document.querySelector('#p-personal-label');
133 var userName = bannerUser ? bannerUser.textContent.trim() : '';
134 if (userName) {
135 var userLink = document.createElement('a');
136 userLink.className = 'relocated-user';
137 userLink.href = mw.util.getUrl('User:' + userName);
138 userLink.title = userName;
139
140 var userIcon = document.createElement('span');
141 userIcon.className = 'relocated-user-icon';
142 userIcon.innerHTML = '👤';
143
144 var userText = document.createElement('span');
145 userText.className = 'relocated-user-name';
146 userText.textContent = userName;
147
148 userLink.appendChild(userIcon);
149 userLink.appendChild(userText);
150 target.appendChild(userLink);
151 }
152 }
153 })();
154
155 // --- Script loaders ---
156 $.getScript(mw.util.getUrl("MediaWiki:ProfileTags.js") + "?action=raw&ctype=text/javascript");
157
158 if (document.getElementById("interactiveMap")) {
159 console.log("map loaded");
160 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
161 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
162 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
163 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap.js") + "?action=raw&ctype=text/javascript", function() {
164 });
165 });
166 });
167 });
168 }
169
170 if (document.getElementById("wardrobe")) {
171 $.getScript(mw.util.getUrl("MediaWiki:Wardrobe.js") + "?action=raw&ctype=text/javascript");
172 console.log("wardrobe loaded");
173 }
174
175 if (document.getElementById("mapEditor")) {
176 $.getScript("https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js", function() {
177 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Objects.js") + "?action=raw&ctype=text/javascript", function() {
178 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/NPCs.js") + "?action=raw&ctype=text/javascript", function() {
179 $.getScript(mw.util.getUrl("MediaWiki:InteractiveMap/Maps.js") + "?action=raw&ctype=text/javascript", function() {
180 $.getScript(mw.util.getUrl("MediaWiki:MapEditor.js") + "?action=raw&ctype=text/javascript", function() {
181 });
182 });
183 });
184 });
185 });
186 }
187
188 if (document.querySelector(".code")) {
189 $.getScript("../custom/highlight.min.js", function() {
190 document.querySelectorAll(".code").forEach(function(el) { hljs.highlightElement(el); });
191 console.log('Code highlighted');
192 });
193 }
194
195 if (document.querySelector('[class*="Tracker"]') && !document.querySelector('[class*="page-MediaWiki"]')) {
196 $.getScript(mw.util.getUrl("MediaWiki:Trackers.js") + "?action=raw&ctype=text/javascript");
197 console.log("Tracker loaded");
198 }
199
200 if (document.getElementById("enemiesDB")) {
201 $.getScript(mw.util.getUrl("MediaWiki:Update_DB.js") + "?action=raw&ctype=text/javascript");
202 console.log("DB Updater Loaded");
203 }
204
205 // --- Hit chance calculator ---
206 function hitChance(accuracy, defence) {
207 accuracy = parseInt(accuracy);
208 defence = parseInt(defence);
209 var hc = 0;
210 if (accuracy >= defence) {
211 hc = 1;
212 } else {
213 hc = 1 / (1 + defence - accuracy);
214 }
215 return (hc * 100).toFixed(2);
216 }
217
218 if (document.querySelector('.hitChance')) {
219 document.querySelectorAll(".hitChance").forEach(function(el) {
220 var parts = el.innerText.split(",");
221 var acc = parts[0];
222 var def = parts[1];
223 el.innerText = "";
224 var accSpan = document.createElement("span");
225 var defSpan = document.createElement("span");
226 var hitSpan = document.createElement("span");
227 var accInput = document.createElement("input");
228 var defInput = document.createElement("input");
229 var hitValueSpan = document.createElement("span");
230
231 accSpan.innerText = "Accuracy:";
232 defSpan.innerText = "Defence:";
233 hitSpan.innerText = "Hit Chance:";
234
235 accInput.type = "number";
236 defInput.type = "number";
237 accInput.min = 0;
238 defInput.min = 0;
239 if (acc !== "0") {
240 accInput.disabled = true;
241 }
242 if (def !== "0") {
243 defInput.disabled = true;
244 }
245 def = def < 0 ? 0 : def;
246 acc = acc < 0 ? 0 : acc;
247 accInput.defaultValue = acc;
248 defInput.defaultValue = def;
249 accInput.onchange = function() {
250 hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
251 };
252 defInput.onchange = function() {
253 hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
254 };
255 el.append(accSpan, accInput, defSpan, defInput, hitSpan, hitValueSpan);
256 hitValueSpan.innerText = hitChance(accInput.value, defInput.value) + "%";
257 });
258 }
259
260 // --- Page Preview Popup ---
261 (function() {
262 var popup = null;
263 var hideTimer = null;
264 var showTimer = null;
265 var previewCache = {};
266
267 function createPopup() {
268 popup = document.createElement('div');
269 popup.className = 'flatmmo-preview';
270 document.body.appendChild(popup);
271
272 popup.addEventListener('mouseenter', function() {
273 clearTimeout(hideTimer);
274 });
275 popup.addEventListener('mouseleave', function() {
276 hideTimer = setTimeout(function() {
277 popup.style.display = 'none';
278 }, 200);
279 });
280 }
281
282 function positionPopup(e) {
283 var x = e.pageX + 15;
284 var y = e.pageY + 15;
285 var popupWidth = 340;
286 var popupHeight = 200;
287
288 if (x + popupWidth > document.documentElement.scrollLeft + window.innerWidth) {
289 x = e.pageX - popupWidth - 15;
290 }
291 if (y + popupHeight > document.documentElement.scrollTop + window.innerHeight) {
292 y = e.pageY - popupHeight - 15;
293 }
294
295 popup.style.left = x + 'px';
296 popup.style.top = y + 'px';
297 }
298
299 function fetchPreview(title, callback) {
300 if (previewCache[title]) {
301 callback(previewCache[title]);
302 return;
303 }
304
305 var apiUrl = mw.util.wikiScript('api') + '?action=parse&page=' + encodeURIComponent(title) + '&prop=text&format=json&redirects=1';
306
307 $.ajax({
308 url: apiUrl,
309 dataType: 'json',
310 success: function(data) {
311 if (data.parse && data.parse.text) {
312 var html = data.parse.text['*'];
313 var temp = document.createElement('div');
314 temp.innerHTML = html;
315
316 var img = null;
317 var infoboxImg = temp.querySelector('.flatmmo-infobox-image img, .flatmmo-infobox img');
318 if (infoboxImg) {
319 img = infoboxImg.src;
320 } else {
321 var firstImg = temp.querySelector('img');
322 if (firstImg && firstImg.width > 20) {
323 img = firstImg.src;
324 }
325 }
326
327 var text = '';
328 var paragraphs = temp.querySelectorAll('p');
329 for (var i = 0; i < paragraphs.length; i++) {
330 var t = paragraphs[i].textContent.trim();
331 if (t.length > 10) {
332 text = t;
333 break;
334 }
335 }
336
337 if (text.length > 250) {
338 text = text.substring(0, 247) + '...';
339 }
340
341 var result = { image: img, text: text, title: title };
342 previewCache[title] = result;
343 callback(result);
344 }
345 },
346 error: function() {
347 console.log('Preview request failed for:', title);
348 }
349 });
350 }
351
352 function buildPopupHTML(data, includeClose) {
353 var html = '';
354 if (includeClose) {
355 html += '<span class="flatmmo-preview-close">×</span>';
356 }
357 html += '<div class="flatmmo-preview-title">' + data.title + '</div>';
358 if (data.image) {
359 html += '<div class="flatmmo-preview-image"><img src="' + data.image + '"></div>';
360 }
361 if (data.text) {
362 html += '<div class="flatmmo-preview-text">' + data.text + '</div>';
363 }
364 if (!data.image && !data.text) {
365 html += '<div class="flatmmo-preview-text" style="color:#999;">No preview available.</div>';
366 }
367 return html;
368 }
369
370 function attachClose() {
371 var closeBtn = popup.querySelector('.flatmmo-preview-close');
372 if (closeBtn) {
373 closeBtn.addEventListener('click', function() {
374 popup.style.display = 'none';
375 });
376 }
377 }
378
379 function showPreview(link, e) {
380 if (!popup) createPopup();
381
382 var href = link.getAttribute('href');
383 if (!href) return;
384
385 var match = href.match(/\/index\.php\/(.+)/);
386 if (!match) return;
387
388 var title = decodeURIComponent(match[1]).replace(/_/g, ' ');
389
390 if (title.match(/^Special:|^Talk:|^User:|^Category:|^File:|^Template:|^MediaWiki:/i)) return;
391 if (href.indexOf('action=edit') !== -1) return;
392 if (link.classList.contains('new')) return;
393
394 var isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
395
396 positionPopup(e);
397 popup.innerHTML = '<span class="flatmmo-preview-close">×</span><div class="flatmmo-preview-title">' + title + '</div><div class="flatmmo-preview-loading">Loading...</div>';
398 popup.style.display = 'block';
399 attachClose();
400
401 fetchPreview(title, function(data) {
402 popup.innerHTML = buildPopupHTML(data, true);
403 attachClose();
404 });
405 }
406
407 var isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
408 var content = document.getElementById('mw-content-text') || document.getElementById('bodyContent') || document.body;
409
410 // Strip all native tooltips from content links
411 $(content).find('a[title]').removeAttr('title');
412
413 if (isMobile) {
414 // Mobile: first tap shows preview, second tap follows link
415 $(content).on('click', 'a:not(.new)', function(e) {
416 var link = this;
417 if ($(link).closest('.flatmmo-preview').length) return;
418
419 var href = link.getAttribute('href');
420 if (!href || !href.match(/\/index\.php\//)) return;
421 if (href.indexOf('action=edit') !== -1) return;
422
423 if (link.getAttribute('data-preview-shown') === 'true') {
424 link.removeAttribute('data-preview-shown');
425 return;
426 }
427
428 e.preventDefault();
429 link.setAttribute('data-preview-shown', 'true');
430 showPreview(link, e);
431
432 setTimeout(function() {
433 link.removeAttribute('data-preview-shown');
434 }, 3000);
435 });
436
437 // Tap outside closes popup
438 $(document).on('click', function(e) {
439 if (popup && !$(e.target).closest('.flatmmo-preview').length && !$(e.target).closest('#mw-content-text a').length) {
440 popup.style.display = 'none';
441 }
442 });
443 } else {
444 // Desktop: hover behavior
445 $(content).on('mouseover', 'a', function(e) {
446 var link = this;
447 if ($(link).closest('.flatmmo-preview').length) return;
448
449 clearTimeout(hideTimer);
450 clearTimeout(showTimer);
451
452 if (link.getAttribute('title')) {
453 link.removeAttribute('title');
454 }
455
456 showTimer = setTimeout(function() {
457 showPreview(link, e);
458 }, 300);
459 });
460
461 $(content).on('mouseout', 'a', function() {
462 clearTimeout(showTimer);
463 hideTimer = setTimeout(function() {
464 if (popup) popup.style.display = 'none';
465 }, 200);
466 });
467 }
468
469 console.log('Page preview popup initialized');
470 // --- Mobile table scroll fix ---
471 (function() {
472 if (window.innerWidth > 768) return;
473 var tables = document.querySelectorAll('#mw-content-text table');
474 for (var i = 0; i < tables.length; i++) {
475 var table = tables[i];
476 if (table.parentElement.classList.contains('table-scroll-wrap')) continue;
477 var wrapper = document.createElement('div');
478 wrapper.className = 'table-scroll-wrap';
479 wrapper.style.overflowX = 'auto';
480 wrapper.style.webkitOverflowScrolling = 'touch';
481 wrapper.style.maxWidth = '100%';
482 wrapper.style.display = 'block';
483 table.parentNode.insertBefore(wrapper, table);
484 wrapper.appendChild(table);
485 }
486 })();
487 })();
488
489 }); // end $(document).ready
