`;
document.body.appendChild(panel);
GM_addStyle(` #readimage-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; background: #f0f0f0; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); font-family: sans-serif; width: 340px; color: #333; } #readimage-settings-body hr { grid-column: 1 / -1; border: none; border-top: 1px solid #ccc; margin: 5px 0; } #readimage-settings-header { padding: 10px; background: #e0e0e0; border-bottom: 1px solid #ccc; cursor: move; display: flex; justify-content: space-between; align-items: center; border-top-left-radius: 8px; border-top-right-radius: 8px; } #readimage-settings-header span { font-weight: bold; } #readimage-close-btn { background: none; border: none; font-size: 16px; cursor: pointer; } #readimage-settings-body { padding: 15px; display: grid; grid-template-columns: auto 1fr; gap: 10px 5px; align-items: center; } #readimage-settings-body label { font-size: 14px; grid-column: 1 / 2; } #readimage-settings-body > *:not(label):not(hr) { grid-column: 2 / 3; } #readimage-settings-body select, #readimage-settings-body textarea { width: 100%; padding: 4px; box-sizing: border-box; } #readimage-settings-body .value-display { font-family: monospace; } #readimage-settings-body div, #readimage-settings-body .radio-group { display: flex; align-items: center; } #readimage-settings-body input[type="range"] { flex: 1; } #readimage-settings-body input[type="color"] { width: 100%; height: 25px; } #readimage-settings-footer { padding: 10px; background: #e0e0e0; text-align: right; border-top: 1px solid #ccc; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; } #readimage-settings-footer button { margin-left: 10px; padding: 5px 15px; border: 1px solid #999; border-radius: 4px; cursor: pointer; } #readimage-save-btn { background: #4CAF50; color: white; border-color: #4CAF50; } #readimage-reset-btn { background: #f44336; color: white; border-color: #f44336; } input:disabled { opacity: 0.5; cursor: not-allowed; } .radio-group label { margin: 0 10px 0 2px; } .settings-help { font-size: 12px; color: #666; margin: 0; } `);
const enableDimmingCheckbox = panel.querySelector('#enableDimming');
const dimmingSlider = panel.querySelector('#dimmingIntensity');
const dimmingValueDisplay = dimmingSlider.nextElementSibling;
function updateSliderState() {
dimmingSlider.disabled = !enableDimmingCheckbox.checked;
dimmingValueDisplay.style.color = enableDimmingCheckbox.checked ? '' : '#aaa';
dimmingSlider.previousElementSibling.style.color = enableDimmingCheckbox.checked ? '' : '#aaa';
}
panel.querySelectorAll('input[type="range"]').forEach(range => { const display = range.nextElementSibling; const container = document.createElement('div'); range.parentNode.insertBefore(container, range); container.appendChild(range); container.appendChild(display); });
const inputs = panel.querySelectorAll('input, select, textarea');
inputs.forEach(input => {
input.addEventListener('input', () => {
const key = input.id || input.name;
const value = input.type === 'checkbox' ? input.checked : input.value;
if (key) config[key] = value;
if (input.type === 'range') { input.nextElementSibling.textContent = `${value}${input.id === 'dimmingIntensity' ? '%' : 'px'}`; }
if (key === 'showFloatingButton') { const button = document.getElementById('readimage-settings-button'); if (config.showFloatingButton) { if (!button) createSettingsButton(); } else { if (button) button.remove(); } return; }
if (input.id.includes('siteList')) return;
updateStyles();
if (key === 'minWidth' || key === 'minHeight' || key === 'style') { document.querySelectorAll('.readimage-processed').forEach(el => { el.classList.remove('readimage-processed'); const marker = el.querySelector('.readimage-marker'); if (marker) marker.remove(); }); debounceProcessImages(); }
updatePanelState();
updateSliderState();
});
});
panel.querySelector('#readimage-save-btn').addEventListener('click', () => { saveConfig(); closeSettingsPanel(); });
panel.querySelector('#readimage-reset-btn').addEventListener('click', resetConfigAndClearData);
panel.querySelector('#readimage-close-btn').addEventListener('click', closeSettingsPanel);
updatePanelState();
updateSliderState(); // 初始化滑块状态
makeDraggable(panel.querySelector('#readimage-settings-header'), panel);
}
function updatePanelState() { const panel = document.getElementById('readimage-settings-panel'); if (!panel) return; const currentStyle = panel.querySelector('#style').value; const unreadColorInput = panel.querySelector('#unreadColor'); const shadowCheckbox = panel.querySelector('#shadow'); const sizeLabel = panel.querySelector('#size-label'); const unreadColorLabel = unreadColorInput.previousElementSibling; unreadColorInput.disabled = false; shadowCheckbox.disabled = (currentStyle === 'circle'); if (currentStyle === 'tag') { sizeLabel.textContent = '字号基准 (px):'; unreadColorLabel.textContent = '已读标签文字颜色:'; } else if (currentStyle === 'circle') { sizeLabel.textContent = '直径 (px):'; unreadColorLabel.textContent = '未读颜色:'; } else { sizeLabel.textContent = '大小 (px):'; unreadColorLabel.textContent = '未读颜色:'; } }
function closeSettingsPanel() { const panel = document.getElementById('readimage-settings-panel'); if (panel) panel.remove(); }
function makeDraggable(header, panel) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
header.onmousedown = (e) => {
if (e.target.id === 'readimage-close-btn') return;
panel.style.transform = 'none';
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; };
document.onmousemove = (e) => {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
panel.style.top = (panel.offsetTop - pos2) + "px";
panel.style.left = (panel.offsetLeft - pos1) + "px";
};
};
}
function createSettingsButton() {
if (document.getElementById('readimage-settings-button')) return;
const button = document.createElement('div');
button.id = 'readimage-settings-button';
button.innerHTML = '⚙️';
document.body.appendChild(button);
GM_addStyle(` #readimage-settings-button { position: fixed; z-index: 99998; width: 40px; height: 40px; background-color: rgba(0, 0, 0, 0.5); color: white; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 24px; cursor: pointer; transition: background-color 0.2s, transform 0.2s; user-select: none; } #readimage-settings-button:hover { background-color: rgba(0, 0, 0, 0.7); transform: rotate(45deg); } `);
button.style.right = config.buttonPos.x;
button.style.bottom = config.buttonPos.y;
let dragState = {};
button.addEventListener('mousedown', (e) => {
if (e.button !== 0) return;
dragState = { isDragging: false, startX: e.clientX, startY: e.clientY, btnStartX: parseFloat(button.style.right), btnStartY: parseFloat(button.style.bottom) };
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
const dx = e.clientX - dragState.startX;
const dy = e.clientY - dragState.startY;
if (!dragState.isDragging && Math.sqrt(dx*dx + dy*dy) > 5) { dragState.isDragging = true; }
if (dragState.isDragging) {
let newX = dragState.btnStartX - dx;
let newY = dragState.btnStartY - dy;
newX = Math.max(0, Math.min(newX, window.innerWidth - button.offsetWidth));
newY = Math.max(0, Math.min(newY, window.innerHeight - button.offsetHeight));
button.style.right = `${newX}px`;
button.style.bottom = `${newY}px`;
}
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
if (dragState.isDragging) {
config.buttonPos = { x: button.style.right, y: button.style.bottom };
const currentConfig = GM_getValue('config', DEFAULTS);
currentConfig.buttonPos = config.buttonPos;
GM_setValue('config', currentConfig);
} else {
const panel = document.getElementById('readimage-settings-panel');
if (panel) { closeSettingsPanel(); } else { showSettingsPanel(); }
}
}
}
// --- 4. 脚本初始化 ---
loadVisitedDb();
updateStyles();
debounceProcessImages();
const observer = new MutationObserver(debounceProcessImages);
observer.observe(document.body, { childList: true, subtree: true });
document.body.addEventListener("mousedown", function(event) {
if (event.target.closest('#readimage-settings-button') || event.target.closest('#readimage-settings-panel')) return;
const link = event.target.closest('a');
if (!link || !link.querySelector('.readimage-marker')) return;
// 为父级