// ==UserScript==
// @name 阅图标记 (Visited Image Marker)
// @namespace RANRAN
// @version 1.0.30
// @description 为您点击过的图片链接添加一个可自定义样式的醒目标记,以方便您识别已阅内容。
// @match http://*/*
// @match https://*/*
// @exclude *://tieba.baidu.com/*
// @exclude *://hi.baidu.com/*
// @exclude *://blog.sina.com.cn/*
// @exclude *://*.blog.sina.com.cn/*
// @exclude *://www.51.la/*
// @exclude *://bbs.aicbbs.com/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// 默认配置
const DEFAULTS = {
style: 'tag',
position: 'top-left',
size: '24',
offsetX: '5',
offsetY: '5',
unreadColor: '#FFFFFF',
readColor: '#FF0000',
shadow: true,
minWidth: '40',
minHeight: '40',
siteListMode: 'blacklist',
siteList: [],
buttonPos: { x: '15px', y: '15px' },
showFloatingButton: true,
enableDimming: true,
dimmingIntensity: '65',
};
// --- 1. 配置与存储管理 ---
let config = {};
let processImagesTimeout;
let visitedLinks = new Set();
const VISITED_LINKS_KEY = 'readimage_visited_links';
const VISITED_LINKS_CAP = 2000;
const SYNC_SAVE_KEY = 'readimage_sync_save';
function canonicalizeUrl(href) {
if (typeof href !== 'string' || href.length === 0) return null;
try {
const url = new URL(href);
return url.origin + url.pathname + url.search;
} catch (e) {
return href.split('#')[0];
}
}
function loadConfig() {
const savedConfig = GM_getValue('config', {});
config = { ...DEFAULTS, ...savedConfig };
}
function shouldScriptRun() {
const currentHost = window.location.hostname;
if (!config.siteList || config.siteList.length === 0) {
return config.siteListMode === 'blacklist';
}
const isOnList = config.siteList.some(site => currentHost.endsWith(site));
return config.siteListMode === 'blacklist' ? !isOnList : isOnList;
}
loadConfig();
GM_registerMenuCommand('设置标记样式 (UI)', showSettingsPanel);
GM_registerMenuCommand('重置设置并清空所有记录', resetConfigAndClearData);
GM_registerMenuCommand('清除当前网站的已读记录', clearCurrentSiteData);
if (config.showFloatingButton) {
createSettingsButton();
}
if (!shouldScriptRun()) {
return;
}
function saveConfig() {
const panel = document.getElementById('readimage-settings-panel');
if (panel) {
config.style = panel.querySelector('#style').value;
config.position = panel.querySelector('#position').value;
config.size = panel.querySelector('#size').value;
config.offsetX = panel.querySelector('#offsetX').value;
config.offsetY = panel.querySelector('#offsetY').value;
config.unreadColor = panel.querySelector('#unreadColor').value;
config.readColor = panel.querySelector('#readColor').value;
config.shadow = panel.querySelector('#shadow').checked;
config.minWidth = panel.querySelector('#minWidth').value;
config.minHeight = panel.querySelector('#minHeight').value;
config.siteListMode = panel.querySelector('input[name="siteListMode"]:checked').value;
const siteListText = panel.querySelector('#siteListArea').value;
config.siteList = siteListText.split('\n').map(s => s.trim()).filter(Boolean);
config.showFloatingButton = panel.querySelector('#showFloatingButton').checked;
config.enableDimming = panel.querySelector('#enableDimming').checked;
config.dimmingIntensity = panel.querySelector('#dimmingIntensity').value;
}
GM_setValue('config', config);
alert('设置已保存!部分设置(如站点列表)需要刷新页面才能生效。');
}
function loadVisitedDb() {
const storedLinks = GM_getValue(VISITED_LINKS_KEY, []);
visitedLinks = new Set(storedLinks);
try {
const syncSavedUrl = localStorage.getItem(SYNC_SAVE_KEY);
if (syncSavedUrl) {
visitedLinks.add(syncSavedUrl);
localStorage.removeItem(SYNC_SAVE_KEY);
saveVisitedDb();
}
} catch (e) { console.error('[readimage] Error accessing localStorage:', e); }
}
function saveVisitedDb() { let linksToSave = Array.from(visitedLinks); if (linksToSave.length > VISITED_LINKS_CAP) { linksToSave = linksToSave.slice(linksToSave.length - VISITED_LINKS_CAP); } GM_setValue(VISITED_LINKS_KEY, linksToSave); }
function addLinkToVisited(href) { const canonicalUrl = canonicalizeUrl(href); if (!canonicalUrl || visitedLinks.has(canonicalUrl)) { return; } try { localStorage.setItem(SYNC_SAVE_KEY, canonicalUrl); } catch (e) { console.error('[readimage] Error writing to localStorage:', e); } visitedLinks.add(canonicalUrl); saveVisitedDb(); }
function removeLinkFromVisited(href) { const canonicalUrl = canonicalizeUrl(href); if (canonicalUrl && visitedLinks.has(canonicalUrl)) { visitedLinks.delete(canonicalUrl); saveVisitedDb(); } }
function clearCurrentSiteData() {
const currentOrigin = window.location.origin;
if (confirm(`确定要清除网站 ${currentOrigin} 的所有已读记录吗?\n此操作不可恢复。`)) {
const oldSize = visitedLinks.size;
const newLinks = Array.from(visitedLinks).filter(link => !link.startsWith(currentOrigin));
visitedLinks = new Set(newLinks);
saveVisitedDb();
const removedCount = oldSize - visitedLinks.size;
alert(`清除了 ${removedCount} 条记录。\n页面即将刷新以应用更改。`);
location.reload();
}
}
function resetConfigAndClearData() {
if (confirm('确定要重置所有设置并清空全部已读记录吗?此操作不可恢复。')) {
config = { ...DEFAULTS };
GM_setValue('config', config);
visitedLinks.clear();
GM_setValue(VISITED_LINKS_KEY, []);
try { localStorage.removeItem(SYNC_SAVE_KEY); } catch (e) {}
alert('已重置所有设置和已读记录!请刷新页面。');
location.reload();
}
}
function applyMarker(link) {
let marker = link.querySelector('.readimage-marker');
const isNewMarker = !marker;
if (isNewMarker) {
marker = document.createElement('span');
marker.className = `readimage-marker style-${config.style}`;
link.appendChild(marker);
}
const canonicalUrl = canonicalizeUrl(link.href);
const isRead = canonicalUrl && visitedLinks.has(canonicalUrl);
marker.classList.toggle('is-read', isRead);
link.classList.toggle('is-read', isRead);
if (config.style === 'tag' && isNewMarker) {
let leaveTimeout = null;
marker.addEventListener('mouseenter', () => {
if (leaveTimeout) {
clearTimeout(leaveTimeout);
leaveTimeout = null;
}
if (link.getAttribute('href')) {
link.setAttribute('data-vim-href', link.getAttribute('href'));
link.removeAttribute('href');
}
});
marker.addEventListener('mouseleave', () => {
leaveTimeout = setTimeout(() => {
if (link.hasAttribute('data-vim-href')) {
link.setAttribute('href', link.getAttribute('data-vim-href'));
link.removeAttribute('data-vim-href');
}
}, 100);
});
// ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
// 【核心修改】在“捕获”阶段监听'click'事件,并彻底阻止其传播
// ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
marker.addEventListener('click', (event) => {
// 彻底阻止事件,不让页面上任何其他脚本(如灯箱)响应
event.preventDefault();
event.stopImmediatePropagation();
const href = link.getAttribute('data-vim-href') || link.href;
const isCurrentlyRead = link.classList.contains('is-read');
if (isCurrentlyRead) {
link.classList.remove('is-read');
marker.classList.remove('is-read');
removeLinkFromVisited(href);
} else {
link.classList.add('is-read');
marker.classList.add('is-read');
addLinkToVisited(href);
}
return false;
}, true); // 末尾的 'true' 开启了事件捕获,这是关键
}
}
function debounceProcessImages() { clearTimeout(processImagesTimeout); processImagesTimeout = setTimeout(processImages, 250); }
function processImages() { const links = document.querySelectorAll('a:has(img):not(.readimage-processed)'); links.forEach(link => { link.classList.add('readimage-processed'); const img = link.querySelector('img'); if (img) { const checkAndApply = (targetImg) => { if (targetImg.naturalWidth >= config.minWidth && targetImg.naturalHeight >= config.minHeight) { applyMarker(link); } }; img.addEventListener('load', () => checkAndApply(img), { once: true }); if (img.complete) { checkAndApply(img); } } }); }
function updateStyles() {
const root = document.documentElement;
root.style.setProperty('--marker-size', `${config.size}px`);
root.style.setProperty('--marker-offset-x', `${config.offsetX}px`);
root.style.setProperty('--marker-offset-y', `${config.offsetY}px`);
root.style.setProperty('--marker-unread-color', config.unreadColor);
root.style.setProperty('--marker-read-color', config.readColor);
const brightnessValue = (100 - config.dimmingIntensity) / 100;
root.style.setProperty('--dimming-brightness', brightnessValue);
let positionCSS = '';
switch (config.position) { case 'top-right': positionCSS = `top: var(--marker-offset-y); right: var(--marker-offset-x);`; break; case 'bottom-left': positionCSS = `bottom: var(--marker-offset-y); left: var(--marker-offset-x);`; break; case 'bottom-right': positionCSS = `bottom: var(--marker-offset-y); right: var(--marker-offset-x);`; break; case 'center': positionCSS = `top: 50%; left: 50%; transform: translate(-50%, -50%);`; break; case 'top-left': default: positionCSS = `top: var(--marker-offset-y); left: var(--marker-offset-x);`; break; }
const shadowStyle = config.shadow ? 'text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;' : 'text-shadow: none;';
const dimmingCSS = config.enableDimming ? ` a.is-read img { filter: brightness(var(--dimming-brightness)); transition: filter 0.3s ease-in-out; } ` : '';
const finalCSS = `
a:has(> .readimage-marker) { position: relative !important; display: inherit !important; }
.readimage-marker { position: absolute; ${positionCSS} z-index: 999; pointer-events: none; transition: all 0.2s ease-in-out; line-height: 1; display: grid; place-items: center; font-weight: bold; ${shadowStyle} }
.readimage-marker.style-star::before { content: '★'; font-size: var(--marker-size); color: var(--marker-unread-color); }
.readimage-marker.style-star.is-read::before { color: var(--marker-read-color); }
.readimage-marker.style-circle::before { content: ''; display: block; width: var(--marker-size); height: var(--marker-size); background-color: var(--marker-unread-color); border-radius: 50%; }
.readimage-marker.style-circle.is-read::before { background-color: var(--marker-read-color); }
.readimage-marker.style-tag { background-color: rgba(0, 0, 0, 0.6); color: white; font-size: calc(var(--marker-size) / 2); padding: 0.2em 0.5em; border-radius: 4px; border: 1px solid rgba(255, 255, 255, 0.2); }
.readimage-marker.style-tag::before { content: '未看'; }
.readimage-marker.style-tag.is-read { background-color: var(--marker-read-color); color: var(--marker-unread-color); }
.readimage-marker.style-tag.is-read::before { content: '已看'; }
.readimage-marker.style-tag {
cursor: pointer;
pointer-events: auto;
transition: transform 0.15s ease, background-color 0.2s ease-in-out;
}
.readimage-marker.style-tag:hover {
transform: scale(1.15);
z-index: 1000;
}
${dimmingCSS}
`;
let styleElement = document.getElementById('readimage-style');
if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = 'readimage-style'; document.head.appendChild(styleElement); }
styleElement.textContent = finalCSS;
document.querySelectorAll('.readimage-marker').forEach(marker => { marker.className = 'readimage-marker'; marker.classList.add(`style-${config.style}`); const link = marker.parentElement; const canonicalUrl = canonicalizeUrl(link.href); const isRead = link.classList.contains('is-read') || (canonicalUrl && visitedLinks.has(canonicalUrl)); marker.classList.toggle('is-read', isRead); });
}
function showSettingsPanel() {
if (document.getElementById('readimage-settings-panel')) return;
const panel = document.createElement('div');
panel.id = 'readimage-settings-panel';
panel.innerHTML = ` <div id="readimage-settings-header"><span>标记样式设置</span><button id="readimage-close-btn" title="关闭">✖</button></div> <div id="readimage-settings-body"> <label>样式:</label> <select id="style"> <option value="star" ${config.style === 'star' ? 'selected' : ''}>五角星 ★</option> <option value="circle" ${config.style === 'circle' ? 'selected' : ''}>圆形 ●</option> <option value="tag" ${config.style === 'tag' ? 'selected' : ''}>标签</option> </select> <label>位置:</label> <select id="position"> <option value="top-left" ${config.position === 'top-left' ? 'selected' : ''}>左上</option> <option value="top-right" ${config.position === 'top-right' ? 'selected' : ''}>右上</option> <option value="bottom-left" ${config.position === 'bottom-left' ? 'selected' : ''}>左下</option> <option value="bottom-right" ${config.position === 'bottom-right' ? 'selected' : ''}>右下</option> <option value="center" ${config.position === 'center' ? 'selected' : ''}>居中</option> </select> <label id="size-label">大小 (px):</label> <input type="range" id="size" min="10" max="50" value="${config.size}"><span class="value-display">${config.size}px</span> <label>水平偏移 (px):</label> <input type="range" id="offsetX" min="-20" max="20" value="${config.offsetX}"><span class="value-display">${config.offsetX}px</span> <label>垂直偏移 (px):</label> <input type="range" id="offsetY" min="-20" max="20" value="${config.offsetY}"><span class="value-display">${config.offsetY}px</span> <label>未读颜色/标签文字:</label> <input type="color" id="unreadColor" value="${config.unreadColor}"> <label>已读颜色:</label> <input type="color" id="readColor" value="${config.readColor}"> <label>为星星/标签加描边:</label> <input type="checkbox" id="shadow" ${config.shadow ? 'checked' : ''}> <hr> <label>已读效果:</label> <div><input type="checkbox" id="enableDimming" ${config.enableDimming ? 'checked' : ''}> <label for="enableDimming" style="margin: 0 0 0 4px;">启用压暗效果</label></div> <label for="dimmingIntensity">压暗强度:</label> <input type="range" id="dimmingIntensity" min="0" max="100" value="${config.dimmingIntensity}"><span class="value-display">${config.dimmingIntensity}%</span> <hr> <label>最小宽度 (px):</label> <input type="range" id="minWidth" min="10" max="200" value="${config.minWidth}"><span class="value-display">${config.minWidth}px</span> <label>最小高度 (px):</label> <input type="range" id="minHeight" min="10" max="200" value="${config.minHeight}"><span class="value-display">${config.minHeight}px</span> <hr> <label>站点管理:</label> <div class="radio-group"> <input type="radio" id="blacklist" name="siteListMode" value="blacklist" ${config.siteListMode === 'blacklist' ? 'checked' : ''}> <label for="blacklist">黑名单模式</label> <input type="radio" id="whitelist" name="siteListMode" value="whitelist" ${config.siteListMode === 'whitelist' ? 'checked' : ''}> <label for="whitelist">白名单模式</label> </div> <label for="siteListArea" style="align-self: start; padding-top: 5px;">网站列表:</label> <textarea id="siteListArea" rows="4" placeholder="每行一个域名,例如 google.com example.org">${config.siteList.join('\n')}</textarea> <label></label> <p class="settings-help">黑名单:脚本在此列表网站上**禁用**。<br>白名单:脚本**仅**在此列表网站上生效。</p> <hr> <label>界面选项:</label> <div><input type="checkbox" id="showFloatingButton" ${config.showFloatingButton ? 'checked' : ''}> <label for="showFloatingButton" style="margin: 0 0 0 4px;">显示悬浮设置按钮</label></div> </div> <div id="readimage-settings-footer"><button id="readimage-save-btn">保存</button></div> `;
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; max-height: 70vh; overflow-y: auto; } #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; } 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-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;
}
if (config.style === 'tag' && event.target.closest('.readimage-marker')) {
return;
}
const link = event.target.closest('a');
if (!link || !link.querySelector('.readimage-marker')) {
return;
}
if (!link.classList.contains('is-read')) {
link.classList.add('is-read');
link.querySelector('.readimage-marker').classList.add('is-read');
addLinkToVisited(link.href);
}
}, true);
})();