Bureaucrats, Moderators (CommentStreams), Interface administrators, Push subscription managers, Suppressors, Administrators
13,446
edits
(clear) |
(add JavaScript-Based Time Zone Detection) |
||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
// Utility function to set/get cookies as a fallback for localStorage | |||
function setCookie(name, value, days) { | |||
const expires = new Date(Date.now() + days * 864e5).toUTCString(); | |||
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`; | |||
} | |||
function getCookie(name) { | |||
const match = document.cookie.match(new RegExp(`(?:^|; )${name}=([^;]*)`)); | |||
return match ? decodeURIComponent(match[1]) : null; | |||
} | |||
mw.hook('wikipage.content').add(function($content) { | |||
// Only process for anonymous users | |||
if (mw.user.isAnon()) { | |||
// Detect user's time zone, with fallback to UTC | |||
let userTimeZone; | |||
try { | |||
userTimeZone = localStorage.getItem('wiki_user_timezone') || getCookie('wiki_user_timezone') || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; | |||
} catch (e) { | |||
console.warn('Failed to detect time zone, defaulting to UTC', e); | |||
userTimeZone = 'UTC'; | |||
} | |||
// Cache time zone in localStorage and cookie for persistence | |||
try { | |||
localStorage.setItem('wiki_user_timezone', userTimeZone); | |||
setCookie('wiki_user_timezone', userTimeZone, 365); | |||
} catch (e) { | |||
console.warn('Failed to cache time zone', e); | |||
} | |||
// Define timestamp selectors, including extension-specific ones | |||
const timestampSelectors = [ | |||
'.mw-changeslist-date', // Recent changes | |||
'.mw-contributions-time', // User contributions | |||
'.timestamp', // General timestamps | |||
'.mw-history-date', // Page history | |||
'.mw-last-modified', // Last edited timestamp | |||
'span.history-user-info span', // User signatures | |||
'.smw-timestamp', // Semantic MediaWiki | |||
'.dpl-timestamp' // DynamicPageList | |||
].join(','); | |||
// Optimize by scoping to main content area | |||
const timestamps = $content.find(timestampSelectors); | |||
// Get user's interface language for internationalization | |||
const userLang = mw.config.get('wgUserLanguage') || 'en-US'; | |||
// Debounced timestamp update for performance | |||
const updateTimestamps = debounce(function() { | |||
timestamps.each(function() { | |||
const $element = $(this); | |||
let utcTime = $element.attr('data-mw-timestamp') || $element.text().trim(); | |||
try { | |||
// Parse UTC timestamp with early error handling | |||
let date; | |||
try { | |||
date = new Date(utcTime + (utcTime.includes('Z') ? '' : ' UTC')); | |||
if (isNaN(date)) throw new Error('Invalid date'); | |||
} catch (e) { | |||
console.warn(`Failed to parse timestamp: ${utcTime}`, e); | |||
return; | |||
} | |||
// Format options for user's time zone and language | |||
const options = { | |||
timeZone: userTimeZone, | |||
year: 'numeric', | |||
month: 'short', | |||
day: 'numeric', | |||
hour: 'numeric', | |||
minute: 'numeric', | |||
second: 'numeric', | |||
hour12: userLang === 'en-US' // 12-hour for en-US, 24-hour for others | |||
}; | |||
const formattedTime = date.toLocaleString(userLang, options); | |||
const timeZoneAbbr = new Intl.DateTimeFormat(userLang, { | |||
timeZone: userTimeZone, | |||
timeZoneName: 'short' | |||
}).formatToParts(date).find(part => part.type === 'timeZoneName')?.value | |||
|| userTimeZone.split('/').pop().replace('_', ' ') || userTimeZone; | |||
// Update timestamp | |||
$element.text(`${formattedTime} (${timeZoneAbbr})`); | |||
$element.attr('title', `Original UTC: ${utcTime}`); | |||
} catch (e) { | |||
console.warn(`Error processing timestamp: ${utcTime}`, e); | |||
} | |||
}); | |||
}, 100); | |||
updateTimestamps(); | |||
} | |||
}); | |||
// Non-intrusive prompt triggered on scroll or time zone mismatch | |||
let prompted = false; | |||
const currentZone = Intl.DateTimeFormat().resolvedOptions().timeZone; | |||
const storedZone = localStorage.getItem('wiki_user_timezone') || getCookie('wiki_user_timezone'); | |||
const dismissed = localStorage.getItem('wiki_user_timezone_prompt_dismissed') || getCookie('wiki_user_timezone_prompt_dismissed'); | |||
if (mw.user.isAnon() && !dismissed && (currentZone !== storedZone || !storedZone)) { | |||
$(window).one('scroll', () => { | |||
if (!prompted) { | |||
mw.notify( | |||
'Timestamps are shown in your local time zone. Click here to change it.', | |||
{ | |||
type: 'info', | |||
autoHide: true, | |||
autoHideSeconds: 10, | |||
tag: 'timezone-prompt', | |||
callback: function() { | |||
const defaultZone = currentZone || 'UTC'; | |||
const selectedZone = prompt('Enter your time zone (e.g., America/New_York):', defaultZone); | |||
if (selectedZone) { | |||
try { | |||
localStorage.setItem('wiki_user_timezone', selectedZone); | |||
setCookie('wiki_user_timezone', selectedZone, 365); | |||
localStorage.setItem('wiki_user_timezone_prompt_dismissed', 'true'); | |||
setCookie('wiki_user_timezone_prompt_dismissed', 'true', 365); | |||
location.reload(); | |||
} catch (e) { | |||
console.warn('Failed to save time zone selection', e); | |||
} | |||
} | |||
} | |||
} | |||
); | |||
prompted = true; | |||
} | |||
}); | |||
} | |||
// Debounce utility | |||
function debounce(func, wait) { | |||
let timeout; | |||
return function() { | |||
clearTimeout(timeout); | |||
timeout = setTimeout(func, wait); | |||
}; | |||
} | |||