NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Sturdy dragable reply box
// @version 0.0.3
// @description Let's you drag a reply box
// @author anone
// @match https://sturdychan.help/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM.registerMenuCommand
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = 'draggableDivPosition';
const targetClasses = ['glass', 'editing', 'reply-form'];
const postMine = ['glass', 'media', 'postMine'];
const postMine2 = ['glass', 'postMine'];
let currentForm = null;
const observer = new MutationObserver((mutations) => {
// Find the first matching form that hasn't been processed
const form = document.querySelector('article.' + targetClasses.join('.'));
if (form && form !== currentForm) {
currentForm = form;
setupDraggableForm(form);
loadPosition(form);
}
const formMines = document.querySelectorAll('article.' + postMine.join('.'));
const formMines2 = document.querySelectorAll('article.' + postMine2.join('.'));
setTimeout(() => {
formMines.forEach(formMine => {
formMine.style.cssText = `
position: relative !important;
z-index: auto !important;
resize: none !important;
overflow: auto !important;
max-height: fit-content !important;
left: auto !important;
right: auto !important;
`;
formMine.style.removeProperty('top');
formMine.style.removeProperty('bottom');
});
formMines2.forEach(formMine => {
formMine.style.cssText = `
position: relative !important;
z-index: auto !important;
resize: none !important;
overflow: auto !important;
max-height: fit-content !important;
left: auto !important;
right: auto !important;
`;
formMine.style.removeProperty('top');
formMine.style.removeProperty('bottom');
});
}, 5000);
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: false,
characterData: false
});
function savePosition(form) {
const position = {
left: form.offsetLeft,
top: form.offsetTop,
timestamp: Date.now()
};
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(position));
console.log('Position saved:', position);
} catch (e) {
console.warn('Failed to save position:', e);
}
}
function loadPosition(form) {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (saved && isValidPosition(saved)) {
form.style.left = `${saved.left}px`;
form.style.top = `${saved.top}px`;
console.log('Position loaded:', saved);
}
} catch (e) {
console.warn('Failed to load position:', e);
}
}
function isValidPosition(pos) {
return Number.isInteger(pos.left) &&
Number.isInteger(pos.top) &&
pos.left >= 0 &&
pos.top >= 0 &&
pos.left < window.innerWidth &&
pos.top < window.innerHeight;
}
function setupDraggableForm(form) {
// Add essential styling
form.style.position = 'fixed';
form.style.zIndex = '9999';
form.style.resize = 'both'; // Enable resizing
form.style.overflow = 'auto';
form.style.maxHeight = '25%';
// Drag functionality
let isDragging = false;
let offset = { x: 0, y: 0 };
const header = form.querySelector('header.spaced');
// Preserve existing positioning if any
const initialLeft = parseFloat(form.style.left) || 20;
const initialTop = parseFloat(form.style.top) || 20;
form.style.left = `${initialLeft}px`;
form.style.top = `${initialTop}px`;
// Header interaction
header.style.cursor = 'move';
header.addEventListener('mousedown', (e) => {
// Ignore clicks on form elements
if (e.target.closest('input, a, button, textarea')) return;
isDragging = true;
offset.x = e.clientX - form.offsetLeft;
offset.y = e.clientY - form.offsetTop;
form.style.zIndex = '10000';
e.preventDefault();
});
// Mouse movement handler
document.addEventListener('mousemove', (e) => {
if (isDragging) {
form.style.left = `${e.clientX - offset.x}px`;
form.style.top = `${e.clientY - offset.y}px`;
savePosition(form);
}
});
// Cleanup on release
document.addEventListener('mouseup', () => {
isDragging = false;
form.style.zIndex = '9999';
});
// Form controls protection
form.querySelectorAll('input, textarea, button').forEach(el => {
el.addEventListener('mousedown', (e) => e.stopPropagation());
});
// Window resize handler
window.addEventListener('resize', () => {
const rect = form.getBoundingClientRect();
form.style.left = `${rect.left}px`;
form.style.top = `${rect.top}px`;
});
const postControls = form.querySelector('#post-controls');
if (postControls) {
// Apply required styles
postControls.style.cssText = `
position: absolute !important;
bottom: 0px !important;
`;
// Additional control styling (optional)
postControls.style.width = 'calc(100% - 20px)';
postControls.style.padding = '10px 0';
}
}
})();