Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save EastSun5566/5c8420a591abd1263ef7dbe28427cf59 to your computer and use it in GitHub Desktop.
Save EastSun5566/5c8420a591abd1263ef7dbe28427cf59 to your computer and use it in GitHub Desktop.
Highlight all `ui-` class on a web page
// ==UserScript==
// @name HackMD `ui-` classes highlighter
// @namespace http://hackmd.io/
// @version 0.1.2
// @description Show all `ui-` classes
// @author stanley2058, Yukaii
// @match https://hackmd.io/*
// @match https://local.localhost/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=hackmd.io
// @grant GM.getValue
// @grant GM.setValue
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// ==/UserScript==
const setVal = (k, v) => typeof GM !== 'undefined' ? GM.setValue(k, v) : Promise.resolve(localStorage.setItem(k, v));
const getVal = (k, v) => typeof GM !== 'undefined' ? GM.getValue(k, v) : Promise.resolve(localStorage.getItem(k) ?? v);
(async function() {
let highlighting = false;
let overlays = new Map();
// Add tooltip styles
const style = document.createElement('style');
style.textContent = `
#highlight-toggle {
position: relative;
}
#highlight-toggle::after {
content: attr(title);
position: absolute;
background: #333;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: pre;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
pointer-events: none;
bottom: 120%;
left: 50%;
transform: translateX(-50%);
}
#highlight-toggle::before {
content: '';
position: absolute;
border: 5px solid transparent;
border-top-color: #333;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s, visibility 0.2s;
pointer-events: none;
bottom: calc(120% - 10px);
left: 50%;
transform: translateX(-50%);
}
#highlight-toggle:hover::after,
#highlight-toggle:hover::before {
opacity: 1;
visibility: visible;
}
`;
document.head.appendChild(style);
function validatePosition(position) {
const defaultPosition = { left: '10px', bottom: '10px' };
if (!position) return defaultPosition;
const left = parseInt(position.left);
const bottom = parseInt(position.bottom);
if (isNaN(left) || isNaN(bottom) ||
left < 0 || left > window.innerWidth - 40 ||
bottom < 0 || bottom > window.innerHeight - 40) {
return defaultPosition;
}
return {
left: `${left}px`,
bottom: `${bottom}px`
};
}
const savedPosition = validatePosition(JSON.parse(await getVal('highlightBtnPosition', null)));
const isVisible = await getVal('highlightBtnVisible') !== 'false';
const toggleBtn = document.createElement('button');
toggleBtn.id = 'highlight-toggle';
toggleBtn.setAttribute('aria-label', 'Toggle UI Class Highlighter');
toggleBtn.setAttribute('title', 'Toggle UI Class Highlighter\nLeft click: Toggle highlight\nRight click: Hide button\nCtrl+click: Remove overlay');
toggleBtn.setAttribute('draggable', 'true');
Object.assign(toggleBtn.style, {
position: 'fixed',
left: savedPosition.left,
bottom: savedPosition.bottom,
zIndex: '10002',
padding: '8px',
background: '#FFA500',
border: 'none',
borderRadius: '50%',
cursor: 'pointer',
width: '40px',
height: '40px',
display: isVisible ? 'flex' : 'none',
alignItems: 'center',
justifyContent: 'center',
opacity: '0.95',
});
toggleBtn.innerHTML = '🔍';
toggleBtn.addEventListener('mouseenter', () => {
toggleBtn.style.background = '#FF8C00';
});
toggleBtn.addEventListener('mouseleave', () => {
toggleBtn.style.background = '#FFA500';
});
const dropOverlay = document.createElement('div');
Object.assign(dropOverlay.style, {
position: 'fixed',
left: 0,
top: 0,
zIndex: '10001',
width: '100dvw',
height: '100dvh',
display: 'none',
});
let isDragging = false;
let currentX;
let currentY;
let initialX;
let initialY;
let xOffset = 0;
let yOffset = 0;
toggleBtn.addEventListener('dragstart', (e) => {
dropOverlay.style.display = 'block';
initialX = e.clientX - xOffset;
initialY = e.clientY - yOffset;
});
dropOverlay.addEventListener('dragover', (e) => {
e.preventDefault();
});
dropOverlay.addEventListener('drop', (e) => {
dropOverlay.style.display = 'none';
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
xOffset = currentX;
yOffset = currentY;
const { width, height } = toggleBtn.getBoundingClientRect();
const left = Math.max(0, Math.min(window.innerWidth - 40, e.clientX - width / 2));
const bottom = Math.max(0, Math.min(window.innerHeight - 40, window.innerHeight - e.clientY - height / 2));
toggleBtn.style.left = `${left}px`;
toggleBtn.style.bottom = `${bottom}px`;
setVal('highlightBtnPosition', JSON.stringify({
left: toggleBtn.style.left,
bottom: toggleBtn.style.bottom
}));
});
toggleBtn.addEventListener('contextmenu', (e) => {
e.preventDefault();
toggleBtn.style.display = 'none';
setVal('highlightBtnVisible', 'false');
});
async function enableUIClassDebug () {
const currentPosition = validatePosition(JSON.parse(await getVal('highlightBtnPosition', null)));
toggleBtn.style.left = currentPosition.left;
toggleBtn.style.bottom = currentPosition.bottom;
toggleBtn.style.display = 'flex';
setVal('highlightBtnVisible', 'true');
setVal('highlightBtnPosition', JSON.stringify(currentPosition));
console.log('UI Class Debug enabled. The button has been restored to a valid position.');
};
window.enableUIClassDebug = enableUIClassDebug;
function getTrackingClasses(element) {
return Array.from(element.classList)
.filter(className => className.startsWith('ui-'));
}
function createOverlay(element) {
const rect = element.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'absolute',
background: 'rgba(255, 255, 0, 0.3)',
border: '2px solid #FFA500',
zIndex: '10000',
left: (rect.left + scrollLeft) + 'px',
top: (rect.top + scrollTop) + 'px',
width: rect.width + 'px',
height: rect.height + 'px'
});
const label = document.createElement('div');
Object.assign(label.style, {
position: 'absolute',
background: '#FFA500',
color: 'black',
padding: '2px 6px',
borderRadius: '3px',
fontSize: '12px',
fontFamily: 'monospace',
zIndex: '10001',
display: 'none',
left: (rect.left + scrollLeft) + 'px',
whiteSpace: 'nowrap',
transform: ''
});
const trackingClasses = getTrackingClasses(element).join(', ');
label.textContent = trackingClasses;
overlay.addEventListener('mouseenter', () => {
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const elementCenterY = rect.top + (rect.height / 2);
const elementCenterX = rect.left + (rect.width / 2);
if (elementCenterY < viewportHeight / 2) {
label.style.top = (rect.bottom + scrollTop + 4) + 'px';
} else {
label.style.top = (rect.top + scrollTop - 24) + 'px';
}
if (elementCenterX < viewportWidth / 2) {
label.style.transform = '';
} else {
label.style.transform = `translateX(calc(-100% + ${rect.width}px))`;
}
label.style.display = 'block';
});
overlay.addEventListener('mouseleave', () => {
label.style.display = 'none';
});
overlay.addEventListener('click', (e) => {
window.navigator.clipboard
.writeText(label.innerText)
.catch(console.error);
if (!e.ctrlKey && !e.metaKey) return;
document.body.removeChild(overlay);
document.body.removeChild(label);
e.preventDefault();
e.stopPropagation();
});
document.body.appendChild(overlay);
document.body.appendChild(label);
return [overlay, label];
}
function toggleHighlights() {
highlighting = !highlighting;
if (highlighting) {
$("[class^='ui-'],[class*=' ui-']").each((_, element) => {
const [overlay, label] = createOverlay(element);
overlays.set(element, [overlay, label]);
});
} else {
for (const [overlay, label] of overlays.values()) {
overlay.remove();
label.remove();
}
overlays.clear();
}
}
toggleBtn.addEventListener('click', toggleHighlights);
document.body.appendChild(dropOverlay);
document.body.appendChild(toggleBtn);
window.addEventListener('keydown', async (e) => {
const shouldTriggerToggle =
(e.ctrlKey && e.shiftKey && e.key === 'U') ||
(e.metaKey && e.shiftKey && e.key === 'u');
const shouldToggleButtonDisplay =
(e.ctrlKey && e.shiftKey && e.key === 'H') ||
(e.metaKey && e.shiftKey && e.key === 'h');
if (shouldTriggerToggle) {
e.preventDefault();
toggleHighlights();
}
if (shouldToggleButtonDisplay) {
e.preventDefault();
const disabled = await getVal('highlightBtnVisible') === 'false';
if (disabled) {
enableUIClassDebug();
} else {
toggleBtn.style.display = 'none';
setVal('highlightBtnVisible', 'false');
}
}
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment